prat
12/28/2020, 4:24 PMmillis value doesn't change when I add a log at A - before Canvas. Without the log or when I add log at B instead, millis gets updated. Is this an expected behavior?
@Composable
fun DrawSomething(
modifier: Modifier,
strokeWidth: Float = 8f
) {
val millis = animationTimeMillis()
// A: when adding a Log here, millis.value will stop at 0 and won't draw arc
// Log.d("DrawSomething", "millis : ${millis.value}")
Canvas(modifier = modifier) {
// B
// Log.d("DrawSomething", "millis : ${millis.value}")
drawArc(
color = Color.Green,
startAngle = 0f,
sweepAngle = 0f + (millis.value / 360),
useCenter = false,
size = Size(100f, 100f),
style = Stroke(width = strokeWidth)
)
}
}
@Composable
fun animationTimeMillis(): State<Long> {
val millisState = mutableStateOf(0L)
val lifecycleOwner = AmbientLifecycleOwner.current
LaunchedEffect(Unit) {
val startTime = withFrameMillis { it }
lifecycleOwner.whenStarted {
while (true) {
withFrameMillis { frameTime ->
millisState.value = frameTime - startTime
}
}
}
}
return millisState
}Adam Powell
12/28/2020, 9:17 PMval millisState = mutableStateOf(0L)
should be:
val millisState = remember { mutableStateOf(0L) }Adam Powell
12/28/2020, 9:18 PMAdam Powell
12/28/2020, 9:20 PMDrawSomething produced an unstable result. Adding a new point of invalidation (reading ${millis.value} in the log statement) triggered the bug.Adam Powell
12/28/2020, 9:21 PMAdam Powell
12/28/2020, 9:22 PMLaunchedEffect in animationTimeMillis uses Unit as its subject key. This is one of those constructs that is always just a little bit unsafe; when you see the use of Unit (or true or any other constant) as an effect subject key and there's a bug at play, there's a good chance this is where it's hiding.Adam Powell
12/28/2020, 9:23 PMwhile (true) - plenty of cases where it's appropriate but it should always make you look closelyAdam Powell
12/28/2020, 9:23 PMAdam Powell
12/28/2020, 9:24 PMLaunchedEffect has two inputs that it closes over that would make good subject keys and would help trace bugs like this: both millisState and lifecycleOwner - if either of these instances change, you may want to restart the LaunchedEffect and stop the old one.Adam Powell
12/28/2020, 9:27 PMmillisState was missing a remember, each time animationTimeMillis recomposed, it created a new MutableState<Long> and returned it, but the LaunchedEffect was still updating the old state object from that initial composition.
Adding the log statement right after the call to animationTimeMillis meant that DrawSomething now recomposes when millis changes for the first time, causing the animationTimeMillis call to recompose, triggering the bug.Adam Powell
12/28/2020, 9:27 PMwithFrameMillis loopAdam Powell
12/28/2020, 9:28 PMmutableStateOf inside of a composable function and suggest surrounding it with a remember {}.prat
12/28/2020, 10:48 PM