Kotlin Flow - 안드로이드 타이머 구현

타이머는 얼마나 복잡합니까?Kotlin 스트림을 구현함으로써 Kotlin 스트림을 심도 있게 파악할 것입니다.
ViewModel의 Kotlin 흐름으로 논리를 구축하고 Jetpack Compose 및 Composable로 타이머를 표시합니다.주 정부는 StateFlow를 대표로 할 것이다.

타이머에 뭐가 있어요?

  • 타이머가 비활성 상태일 때 초기 상태가 있습니다.
  • 클릭하면 카운트다운이 됩니다.
  • 완성되면 리셋됩니다.
  • 코드 보기 here it is 를 통해 빠르게 시작하고 싶으면.

    사용자 인터페이스



    타이머의 UI 부분은 CircularProgressIndicator와 카운트다운 수치를 표시하는 텍스트로 표시됩니다.타이머는 클릭할 때만 작동하고, 다른 클릭은 타이머를 리셋합니다.
    다음은 사용자 인터페이스 코드입니다.
    @Composable
    fun TimerDisplay(timerState: TimerState, toggleStartStop: () -> Unit) {
        Box(contentAlignment = Alignment.Center) {
            CircularProgressIndicator(
                timerState.progressPercentage,
                Modifier.clickable { toggleStartStop() })
    
            Text(timerState.displaySeconds)
        }
    }
    
    TimerState는 지원 클래스입니다.60초 타이머가 30초 경과할 때CircularProgressIndicator의 진도는 0.5%, 나머지 몇 초 안에 표시되는 텍스트만 포함됩니다.
    TimerState를 사용하면 남은 초와 총 초를 제공하고 조합할 수 있는 나머지 정보를 계산할 수 있습니다.
    TimerState 코드입니다.
    data class TimerState(
        val secondsRemaining: Int? = null,
        val totalSeconds: Int = 60,
        val textWhenStopped: String = "-"
    ) {
        val displaySeconds: String = 
             (secondsRemaining ?: textWhenStopped).toString()
    
        // Show 100% if seconds remaining is null
        val progressPercentage: Float = 
            (secondsRemaining ?: totalSeconds) / totalSeconds.toFloat()
    
        // Always implement toString from Effective Java Item 9
        override fun toString(): String = "Seconds Remaining $secondsRemaining, totalSeconds: $totalSeconds, progress: $progressPercentage"
    
    }
    
    너는 그 중의 약간의 미세한 차이를 읽거나 뛰어넘을 수 있다.미세한 차이는 타이머가 멈췄을 때 당신은 무엇을 나타낼 수 있습니까?이 데이터 클래스에 대해 stopped는 int에서 표시하고 나머지 초는null입니다.초수 Maining과 total Seconds만 제공하면 TimerDisplay에 필요한 나머지 정보를 계산할 수 있습니다.

    논리적 흐름


    타이머의 논리를 TimerUseCase라는 클래스에 봉인할 것입니다.
    다음은 그것의 작업 원리다.
    (totalSeconds downTo 0).asFlow()
    
    총 초에서 0까지 숫자 목록을 효과적으로 만들고 흐르는 형식으로 하나씩 보냅니다.
    토탈섹스가 5라면 5, 4, 3, 2, 1, 0의 발사를 받을 수 있다.
    마지막 코드에서 우리는 그것을 1로 줄일 것이지만, 잠시 후에 우리는 원인을 볼 것이다., 이것은 온스타트와 관계가 있다.
    (totalSeconds downTo 0).asFlow()
    .onEach { delay(1000) }
    
    즉, 항목이 이 흐름에서 나올 때마다 먼저 1초를 기다린 다음 체인을 따라 계속하도록 한다.
    이것이 바로 타이머가 똑딱거리는 실현 방식이다.
    .transform { remainingSeconds: Int ->
                    emit(TimeState(remainingSeconds))
                }
    
    이것은 아마도 다음일 것이다. 그러나 우리가 이렇게 쓰면 문제가 있을 것이다.
    여기서 우리가 유일하게 해야 할 일은 시간 상태를 만드는 것이다. 그러나 더 복잡한 작업을 수행하려면 몇 밀리초가 걸릴 수도 있다. 현재 우리는 체인에서 시간을 강제로 이동시킨다.
    여기에 예가 하나 있다.다음 발사에 1초가 걸리지만 Time State와 같은 대상을 만드는 데 200ms가 걸리면 다음 항목을 발사하기 전에 1200ms가 지났습니다.만약 이 주기가 타이머에서 여러 번 반복된다면 더 이상 정확하지 않을 것이다.
    그래서 우리는 둘 사이에 끼어 있는 것이 필요하다.다음은 'conflate' 를 사용하는 실제 코드입니다. 하나의 단독 라인에서 (동시에) 변환 함수를 실행하는 데 사용되며, 하나의 시간 라인에서 실행되지 않습니다.
    또한 코드가 그대로 유지되면, 타이머가 1초 동안 클릭한 후에 똑딱거리는 소리만 볼 수 있다.우리는 그것이 즉시 완전한 시간을 보여 주고 선택을 시작하기를 바란다. 그래서 우리는 두 가지 수정을 했다.
  • 유량이 연결될 때 우리는 즉시 총 초수를 카운트다운의 첫 번째 값으로 보낸다.이것은 onStart를 사용하여 totalSeconds를 보내는 것을 의미한다.
  • .onStart { emit(totalSeconds) }
    
    그리고 흐름은 사실상 첫 번째 지연치를 낸다.다음 초 그거.이것이 바로 왜 절차가
    (totalSeconds - 1 downTo 0).asFlow()
    
    다음은 코드입니다.
        /**
         * The timer emits the total seconds immediately.
         * Each second after that, it will emit the next value.
         */
        fun initTimer(totalSeconds: Int): Flow<TimeState> =
            (totalSeconds - 1 downTo 0).asFlow() // Emit total - 1 because the first was emitted onStart
                .onEach { delay(1000) } // Each second later emit a number
                .onStart { emit(totalSeconds) } // Emit total seconds immediately
                .conflate() // In case the creating of State takes some time, conflate keeps the time ticking separately
                .transform { remainingSeconds: Int ->
                    emit(TimeState(remainingSeconds))
                }
    

    부가 조건


    우리 아직 안 끝났어!
    실제로 계수가 완료되면 타이머는 기본값으로 취소되거나 재설정된 문제를 처리할 수 없습니다.이를 위해서는 시작할 때 onCompletion을 설정해야 합니다.
    이것은 다음과 같습니다.
    private var _timerStateFlow = MutableStateFlow(TimerState())
    val timerStateFlow: StateFlow<TimerState> = _timerStateFlow
    
    Timer State를 보낼 수 있는 개인 상태 흐름을 만들고, 조합 가능한 상태로 연결할 수 있는 공공 상태 흐름을 만들어야 합니다.
        private var job: Job? = null
    
        fun toggleTime(totalSeconds: Int) {
            if (job == null) {
                job = timerScope.launch {...}
            } else {
                job?.cancel()
                job = null
            }
        }
    
    ToggleTime 함수를 호출하면 협동 프로그램 작업이 실행되지 않으면 새로운 작업이 시작됩니다.
    다시 클릭하면 현재 실행 중인 작업이 취소됩니다.
    job = timerScope.launch {
                    initTimer(totalSeconds)
                        .onCompletion { _timerStateFlow.emit(TimerState()) }
                        .collect { _timerStateFlow.emit(it) }
                }
    
    onCompletion 블록은 "finally"와 같습니다.흐름이 정상적으로 완성되었든지 오류가 있든지 간에 onCompletion 블록을 호출하고 시간 상태를 리셋하여 UI를 리셋할 수 있도록 합니다.
    마찬가지로collect에서 우리는 받은 Time State를 Timer State Flow에 넣고 UI에서 관찰할 수 있도록 합니다.
    모두 여기 있습니다.
    class TimerUseCase(private val timerScope: CoroutineScope) {
    
    private var _timerStateFlow = MutableStateFlow(TimerState())
    val timerStateFlow: StateFlow<TimerState> = _timerStateFlow
    
    private var job: Job? = null
    
    fun toggleTime(totalSeconds: Int) {
        if (job == null) {
            job = timerScope.launch {
                initTimer(totalSeconds)
                    .onCompletion { _timerStateFlow.emit(TimerState()) }
                    .collect { _timerStateFlow.emit(it) }
            }
        } else {
            job?.cancel()
            job = null
        }
    }
    /**
     * The timer emits the total seconds immediately.
     * Each second after that, it will emit the next value.
     */
    private fun initTimer(totalSeconds: Int): Flow<TimerState> =
    //        generateSequence(totalSeconds - 1 ) { it - 1 }.asFlow()
        (totalSeconds - 1 downTo 0).asFlow() // Emit total - 1 because the first was emitted onStart
            .onEach { delay(1000) } // Each second later emit a number
            .onStart { emit(totalSeconds) } // Emit total seconds immediately
            .conflate() // In case the operation onTick takes some time, conflate keeps the time ticking separately
            .transform { remainingSeconds: Int ->
                emit(TimerState(remainingSeconds))
            }
    }
    }
    

    ViewModel


    예를 들어 이 모든 작업을 완성하면 ViewModel을 매우 깨끗하게 할 수 있습니다.
    class TimerVm : ViewModel() {
    
        private val timerIntent = TimerUseCase(viewModelScope)
        val timerStateFlow: StateFlow<TimerState> = timerIntent.timerStateFlow
    
        fun toggleStart() = timerIntent.toggleTime(60)
    }
    
    마지막으로 주요 활동에 사용됩니다. 예를 들어 다음과 같습니다.
    val vm = viewModel<TimerVm>()
    val timerState = vm.timerStateFlow.collectAsState()
    TimerDisplay(timerState.value, vm::toggleStart)
    
    너는 지금 아주 좋은 타이머가 하나 생겼다. 타이머는 줄곧 규칙을 지킬 것이다.이 과정에서 당신은 포장, 협동 프로그램과 Jetpack에 대한 지식을 배웠을 것입니다!
    저는 안드로이드의 고급 직위를 찾고 있습니다. 이 직위는 고객에게 큰 영향을 미치고 좋은 팀을 가지고 임금도 높습니다.만약 당신이 모집하고 있다면 저에게 알려주세요!컨설팅 계약도 가능하다.

    좋은 웹페이지 즐겨찾기