Kotlin Time (Duration) & Flow
withTimeout
Section titled “withTimeout”Modifier.pointerInput(Unit) { detectTapGestures(onDoubleTap = { togglePlay() }, onPress = { mediaPlayer?.let { try { withTimeout(500) { tryAwaitRelease() } } catch (e: TimeoutCancellationException) { it.playbackParams = PlaybackParams().apply { speed = 2f } tryAwaitRelease() it.playbackParams = PlaybackParams().apply { speed = 1f } updateState() } } })}Countdown Timer Patterns
Section titled “Countdown Timer Patterns”❌ Avoid — Manual math + CountDownTimer
Section titled “❌ Avoid — Manual math + CountDownTimer”val day = 60 * 60 * 24val hour = 60 * 60val days = countdown / dayval hours = (countdown % day) / hourval minutes = ((countdown % day) % hour) / 60val seconds = ((countdown % day) % hour) % 60✅ Use — Duration.toComponents
Section titled “✅ Use — Duration.toComponents”val duration = (targetTime - now).secondsduration.toComponents { days, hours, minutes, seconds, _ -> // each unit is already broken down for you}Time APIs
Section titled “Time APIs”Instant — simpler epoch time
Section titled “Instant — simpler epoch time”// instead of LocalDateTime.now().atZone(...).toEpochSecond()val now = Instant.now().epochSecondDuration.between — diff between two times
Section titled “Duration.between — diff between two times”val target = LocalDateTime.parse("2026-05-26 23:59:59", pattern)val duration = Duration.between(LocalDateTime.now(), target)duration.toKotlinDuration().toComponents { days, hours, minutes, seconds, _ -> }until — readable unit difference
Section titled “until — readable unit difference”val days = LocalDate.now().until(targetDate, ChronoUnit.DAYS)val hours = LocalDateTime.now().until(targetDateTime, ChronoUnit.HOURS)Coroutines
Section titled “Coroutines”❌ Avoid — runBlocking
Section titled “❌ Avoid — runBlocking”Blocks the thread, dangerous on the main thread.
runBlocking { while (true) { delay(1000) }}✅ Use — CoroutineScope.launch
Section titled “✅ Use — CoroutineScope.launch”Non-blocking, respects cancellation.
CoroutineScope(Dispatchers.Main).launch { while (isActive) { // ... delay(1000) }}✅ Better — viewModelScope / lifecycleScope
Section titled “✅ Better — viewModelScope / lifecycleScope”Auto-cancels when the lifecycle ends, no manual cleanup needed.
viewModelScope.launch { while (isActive) { // ... delay(1000) }}Flow — Most Idiomatic Ticker
Section titled “Flow — Most Idiomatic Ticker”Separates ticking logic from UI, easy to test and reuse.
flow { while (true) { emit(Duration.between(LocalDateTime.now(), target).toKotlinDuration()) delay(1000) }}.takeWhile { it.isPositive() } // auto-stops at zero.collect { duration -> duration.toComponents { days, hours, minutes, seconds, _ -> // update UI }}Key Flow operators
Section titled “Key Flow operators”| Operator | Purpose |
|---|---|
emit() | push a value downstream |
collect {} | consume values |
takeWhile {} | auto-complete when condition is false |
map {} | transform each value |
Cleanest Full Example
Section titled “Cleanest Full Example”val pattern = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")val target = LocalDateTime.parse("2026-05-26 23:59:59", pattern)val views = RemoteViews(context.packageName, R.layout.countdown_widget)
flow { while (true) { emit(Duration.between(LocalDateTime.now(), target).toKotlinDuration()) delay(1000) }}.takeWhile { it.isPositive() }.collect { duration -> duration.toComponents { days, hours, minutes, seconds, _ -> mapOf( R.id.days to days, R.id.hours to hours, R.id.minutes to minutes, R.id.seconds to seconds, ).forEach { (id, value) -> views.setTextViewText(id, value.toString()) } appWidgetManager.updateAppWidget(appWidgetId, views) }}Quick Reference
Section titled “Quick Reference”| Need | Use |
|---|---|
| Current epoch seconds | Instant.now().epochSecond |
| Diff between two times | Duration.between(a, b) |
| Break duration into units | .toKotlinDuration().toComponents {} |
| Tick every second | flow { while(true) { emit(...); delay(1000) } } |
| Auto-stop at zero | .takeWhile { it.isPositive() } |
| Safe coroutine scope | viewModelScope or lifecycleScope |