Как создать обратный отсчет с помощью сопрограмм потока
Я пытаюсь создать поток с сопрограммами, но это не дает мне ожидаемого результата. Я бы хотел указать время истечения срока действия (неважно, в миллисекундах, секундах и т. Д.), Когда время достигает 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>
}