Quiero implementar un temporizador usando corrutinas de Kotlin, algo similar a esto implementado con RxJava:
Flowable.interval(0, 5, TimeUnit.SECONDS) .observeOn(AndroidSchedulers.mainThread()) .map { LocalDateTime.now() } .distinctUntilChanged { old, new -> old.minute == new.minute } .subscribe { setDateTime(it) }
Emitirá LocalDateTime cada nuevo minuto.
Editar : tenga en cuenta que la API sugerida en la respuesta original ahora está marcada como @ObsoleteCoroutineApi
:
Los canales de teletipo no están integrados actualmente con la concurrencia estructurada y su API cambiará en el futuro.
Ahora puede usar la API de Flow
para crear su propio flujo de teletipo:
import kotlin.time.Duration import kotlin.time.Duration.Companion.seconds import kotlinx.coroutines.* import kotlinx.coroutines.flow.* fun tickerFlow(period: Duration, initialDelay: Duration = Duration.ZERO) = flow { delay(initialDelay) while (true) { emit(Unit) delay(period) } }
Y puedes usarlo de una manera muy similar a tu código actual:
tickerFlow(5.seconds) .map { LocalDateTime.now() } .distinctUntilChanged { old, new -> old.minute == new.minute } .onEach { setDateTime(it) } .launchIn(viewModelScope) // or lifecycleScope or other
Nota: con el código tal como está escrito aquí, tickerFlow
no tiene en cuenta el tiempo necesario para procesar elementos, por lo que es posible que el retraso no sea regular (es un retraso entre el procesamiento de elementos). Si desea que el ticker marque independientemente del procesamiento de cada elemento, es posible que desee utilizar un búfer o un subproceso dedicado (por ejemplo, a través flowOn
).
Creo que todavía es experimental, pero puede usar un TickerChannel para producir valores cada X milis:
val tickerChannel = ticker(delayMillis = 60_000, initialDelayMillis = 0) repeat(10) { tickerChannel.receive() val currentTime = LocalDateTime.now() println(currentTime) }
Si necesita continuar con su trabajo mientras su "suscripción" hace algo por cada "tick", puede launch
una rutina de fondo que leerá desde este canal y hará lo que desee:
val tickerChannel = ticker(delayMillis = 60_000, initialDelayMillis = 0) launch { for (event in tickerChannel) { // the 'event' variable is of type Unit, so we don't really care about it val currentTime = LocalDateTime.now() println(currentTime) } } delay(1000) // when you're done with the ticker and don't want more events tickerChannel.cancel()
Si desea detenerse desde dentro del bucle, simplemente puede salir de él y luego cancelar el canal:
val ticker = ticker(500, 0) var count = 0 for (event in ticker) { count++ if (count == 4) { break } else { println(count) } } ticker.cancel()
Un enfoque muy pragmático con Kotlin Flows podría ser:
// Create the timer flow val timer = (0..Int.MAX_VALUE) .asSequence() .asFlow() .onEach { delay(1_000) } // specify delay // Consume it timer.collect { println("bling: ${it}") }
otra posible solución como una extensión kotlin reutilizable de CoroutineScope
fun CoroutineScope.launchPeriodicAsync( repeatMillis: Long, action: () -> Unit ) = this.async { if (repeatMillis > 0) { while (isActive) { action() delay(repeatMillis) } } else { action() } }
y luego el uso como:
var job = CoroutineScope(Dispatchers.IO).launchPeriodicAsync(100) { //... }
y luego para interrumpirlo:
job.cancel()