https://kotlinlang.org logo
Title
t

Travis Griggs

03/07/2023, 1:03 AM
I have a Compose Text that I want to update every second, because it shows a remaining time. Is the following the right/idiomatic way to express that?
var remaining by remember { mutableStateOf(keyInfo.remaining) }
LaunchedEffect(remaining) {
   delay(1.seconds)
   remaining = keyInfo.remaining
}
SettingsLabel(text = remaining.clockPrinted())
e

ephemient

03/07/2023, 3:05 AM
there is no guarantee that the delay will resume or that the composition will be recomposed immediately - it is likely to drift
what you actually want to do is adjust the delay based on the current time, so that it's independent of any additional delays elsewhere, such as
private fun Duration.truncatedTo(unit: Duration): Duration = unit.multipliedBy(this.dividedBy(unit))

@Composable
fun CountDownTimer(
    goal: Instant = Instant.ofEpochSecond(Int.MAX_VALUE.toLong()),
    tick: Duration = Duration.ofSeconds(1L),
    clock: Clock = Clock.systemUTC(),
) {
    val remaining by flow {
        do {
            val remaining = Duration.between(clock.instant(), goal)
            var next = remaining.truncatedTo(tick)
            if (next >= remaining) next -= tick
            next = next.coerceAtLeast(Duration.ZERO)
            delay((remaining - next).toMillis())
            emit(next)
        } while (next > Duration.ZERO)
    }.collectAsState(Duration.between(clock.instant(), goal).coerceAtLeast(Duration.ZERO).truncatedTo(tick))
    Text(text = remaining.toString())
}
(Android has https://developer.android.com/reference/android/os/CountDownTimer which also has similar functionality)
l

LeoColman

03/07/2023, 11:46 PM
Sharing a bit on how I solved this particular issue
Mainly this bit:
@IgnoredOnParcel
  val millisLeft = flow {
    while (true) {
      emit(calculateMillisLeft())
      delay(3)
    }
  }
The critical part is the
calculateMillisLeft
. As @ephemient pointed out, we want to avoid losing the small delays here and there.
e

ephemient

03/08/2023, 5:08 AM
having a wakeup every 3ms is absolute overkill
d

Davide Giuseppe Farella

03/09/2023, 12:43 PM
Honestly, I would deal with it in the ViewModel and submit the state in a flow
e

ephemient

03/10/2023, 8:04 AM
Sure, but wherever you implement it, you have to deal with delay skew