Как создать обратный отсчет с помощью сопрограмм потока

Я пытаюсь создать поток с сопрограммами, но это не дает мне ожидаемого результата. Я бы хотел указать время истечения срока действия (неважно, в миллисекундах, секундах и т. Д.), Когда время достигает 0, обратный отсчет останавливается. Что у меня сейчас есть:

      private fun tickerFlow(start: Long, end: Long = 0L) = flow {
        var count = start
        while (count >= end) {
            emit(Unit)
            count--
            delay(1_000L)
        }
    }

Затем я вызываю эту функцию как:

      val expireDate = LocalDateTime.now().plusSeconds(10L).toEpochSecond(ZoneOffset.UTC)
                tickerFlow(expireDate)
                    .map { LocalDateTime.now().toEpochSecond(ZoneOffset.UTC) - expireDate }
                    .distinctUntilChanged { old, new ->
                        old == new
                    }
                    .onEach {
                        //Here I should print the timer going down with this pattern 
                        //00h: 00m: 00s I did it with String.format("%02dh: %02dm: %02ds") and it works though.
                    }
                    .onCompletion {
                        //Setting the text when completed
                    }
                    .launchIn(scope = scope)

Но даже с этим тестом, что я пытаюсь иметь время истечения 10 секунд, он не печатается и не заканчивается, как я. Я что-то упускаю? Есть ли способ передать местное время даты, чтобы у меня были часы, минуты и секунды? возможно, мне нужно провести расчеты, чтобы получить секунды, минуты, часы из мили / секунды.

TL; DR;

Я получаю от бэкэнда дату истечения срока действия, и я хочу знать, когда этот срок истечения истекает, поэтому мне нужно вычислить его с помощью now () и проверить, когда он истек.

3 ответа

Здесь не нужен поток. Попробуйте этот код:

      val expireDate = LocalDateTime.now().plusSeconds(10L).toEpochSecond(ZoneOffset.UTC)
val currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
for (i in (expireDate - currentTime)  downTo 1) {
    println("$i seconds remaining") // Format the remaining seconds however you wish
    delay(1_000) // Delay for 1 second
}
println("TIME UP") // Run your completion code here

Кроме того, этот код безопасно запускать в основном потоке, поскольку delayне блокирует.

В вашем коде проблема в том, что вы передаете себя в tickerFlow. expireDateсодержит время в секундах от эпохи, а не разницу в секундах от текущего времени. Просто пройти expireDate - LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)в tickerFlow и все заработает.

EDIT: полная реализация с использованием потока

      private fun tickerFlow(start: Long, end: Long = 0L) = flow {
    for (i in start downTo end) {
        emit(i)
        delay(1_000)
    }
}
      val expireDate = LocalDateTime.now().plusSeconds(10L).toEpochSecond(ZoneOffset.UTC)
val currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
tickerFlow(expireDate - currentTime)
    .onEach { secondsRemaining ->
        // Format and display the time
    }
    .onCompletion { 
        // Handle completion
    }
    .launchIn(scope)

Решение с flow

      private fun countDownFlow(
        start: Long,
        delayInSeconds: Long = 1_000L,
    ) = flow {
        var count = start
        while (count >= 0L) {
            emit(count--)
            delay(delayInSeconds)
        }
    }

А затем, учитывая дату истечения срока действия, получите текущую дату, вычтите их и передайте ее как начало.

      val expireDate = LocalDateTime.now().plusSeconds(10L).toEpochSecond(ZoneOffset.UTC)
val currentTime = LocalDateTime.now().toEpochSecond(ZoneOffset.UTC)
tickerFlow(expireDate - currentTime)
                    .onEach {
                        binding.yourTimer.text = String.format(
                            "%02dh: %02dm: %02ds",
                            TimeUnit.SECONDS.toHours(it),
                            TimeUnit.SECONDS.toMinutes(it),
                            TimeUnit.SECONDS.toSeconds(it),
                        )
                    }
                    .onCompletion {
                        //Update UI
                    }
                    .launchIn(coroutineScope)

Если спешите, скопируйте/вставьте это!

      
     fun getFlow( delayTimeMilliseconds: Long,  startValue: Long,  stepValue : Long = 1,  endValue : Long =0): Flow<Long> =
            ( startValue downTo  endValue step  stepValue ).asFlow().flowOn(Dispatchers.IO)
                    .onEach { delay( delayTimeMilliseconds) } 
                    .onStart { emit( startValue) } 
                    .conflate()
                    .transform { remainingValue: Long ->
                        if(remainingValue<0) emit(0)
                        else emit( remainingValue)
                    }

В моем производственном приложении я использую таймер в качестве варианта использования в соответствии с чистой архитектурой. Как это :

      class TimerFlowUseCase constructor() : StateFlowUseCase<TimerFlowUseCase.Params, Long>() {

    override suspend fun getFlow(params: Params): Flow<Long> =
            (params.startValue downTo params.endValue step params.stepValue ).asFlow().flowOn(Dispatchers.IO)
                    .onEach { delay(params.delayTimeMilliseconds) } 
                    .onStart { emit(params.startValue) } // Emits total value on start
                    .conflate()  
                    .transform { remainingValue: Long ->
                        if(remainingValue<0) emit(0)
                        else emit( remainingValue)
                    }

    data class Params( val delayTimeMilliseconds: Long, val startValue: Long, val stepValue : Long = 1, val endValue : Long =0)
}

Где суперкласс:

      abstract class StateFlowUseCase<P, R> {
    suspend operator fun invoke(params: P , coroutineScope: CoroutineScope): StateFlow<R> {
        return getFlow(params).stateIn(coroutineScope)
    }
    abstract suspend fun getFlow(params: P): Flow<R>
}
Другие вопросы по тегам