처음 코드를 작성했을 때는 타이머 로직 완성을 위해 단일 타이머로 개발을 했다.
구현체도 단일 로직에 맞추어 개발을 진행했다.
그렇다 보니, TimerRepository 인스턴스 수는 Singleton이므로 항상 1개였다.
동일 구현체를 활용하여 각각 다른 두 개의 인스턴스를 만들어야했다.
구현체는 단일 타이머를 기준으로 만들었기에, 그대로 사용하면 아래와 같은 문제가 발생했다.
이러면 UI 입장에선 어떤 타이머가 업데이트한 건지 알 수 없다.
이를 통해 해결했다. 저걸 붙여놨으니 에러가 나지……
다만 기존 절대시간 방식이 마음에 들지 않아 delta 방식으로 코드를 변경했다.
/* 기존 방식*/
class TimerRepositoryImpl @Inject constructor() : TimerRepository {
/**
* 타이머 현재 경과 시간
* 변수 명 뒤에 Millis를 붙여 이 변수가 어떤 단위인지 표기.
**/
private val _timeElapsedMillis = MutableStateFlow(0L)
private var timerJob: Job? = null
/**
* 타이머의 최신 재개 시간
**/
private var startTimeMillis: Long = 0L
/**
* 일시 정지에 따른 타이머 누적 시간
**/
private var accumulatedTimeMillis: Long = 0L
private val timerScope = CoroutineScope(Dispatchers.Default)
override fun getTimerMillsUpdate(): Flow<Long> {
return _timeElapsedMillis.asStateFlow()
}
/**
* 타이머 시작
*/
override fun startTimer() {
if (timerJob?.isActive == true) return
startTimeMillis = System.currentTimeMillis()
timerJob = timerScope.launch {
while (true) {
/* Compose에서 delay가 없다면 UI는 event를 잔뜩 받고 성능이 저하됨. 따라서, 적절한 딜레이로 정확도와 성능 개선 */
delay(100)
val currentTimeMillis = System.currentTimeMillis()
val elapsedTimeMillis = currentTimeMillis - startTimeMillis + accumulatedTimeMillis
_timeElapsedMillis.value = elapsedTimeMillis
}
}
}
/**
* 타이머 일시 정지
*/
override fun pauseTimer() {
timerJob?.cancel()
timerJob = null
/* 경과 시간을 누적 시간에 저장 */
accumulatedTimeMillis = _timeElapsedMillis.value
}
/**
* 타이머 종료
*/
override fun stopTimer() {
timerJob?.cancel()
timerJob = null
/* 전부 초기화 */
startTimeMillis = 0L
accumulatedTimeMillis = 0L
_timeElapsedMillis.value = 0L
}
}
변경된 delta 방식
class TimerRepositoryImpl @Inject constructor() : TimerRepository {
private val _flow = MutableStateFlow(0L)
override fun getTimerMillsUpdate(): Flow<Long> = _flow.asStateFlow()
private var timerJob: Job? = null
private var lastTimestamp = 0L
private var accumulated = 0L
private val scope = CoroutineScope(Dispatchers.Default)
override fun startTimer() {
if (timerJob != null) return
lastTimestamp = System.currentTimeMillis()
timerJob = scope.launch {
while (true) {
delay(100)
val now = System.currentTimeMillis()
val delta = now - lastTimestamp
lastTimestamp = now
accumulated += delta
_flow.value = accumulated
}
}
}
override fun pauseTimer() {
timerJob?.cancel()
timerJob = null
}
override fun stopTimer() {
timerJob?.cancel()
timerJob = null
accumulated = 0L
lastTimestamp = 0L
_flow.value = 0L
}
}
로직을 더 간단하게 개선함으로써 잠재적인 버그 가능성을 좀 낮추었다.