Alexander Maryanovsky
03/08/2023, 12:05 PMboundsInWindow
is not recomputed when the size changes (🧵)@Composable
fun DerivedStateTest(){
var size by remember { mutableStateOf(180f) }
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center
){
var coords by remember { mutableStateOf<LayoutCoordinates?>(null) }
val boundsInWindow = remember(coords) {
println("Recomputing")
coords?.boundsInWindow()
}
println("coords: $coords")
println("coords.boundsInWindow: ${coords?.boundsInWindow()}")
println("boundsInWindow: $boundsInWindow")
Box(
modifier = Modifier
.size(size.dp)
.background(Color.Red)
.onGloballyPositioned {
coords = it
}
){
Column(
modifier = Modifier.align(Alignment.Center)
){
Text(
text = "Drop here",
)
Button(
onClick = { size = 380-size }
){
Text("Change size")
}
}
}
}
}
LayoutCoordinates
object is actually InnerNodeCoordinator
which doesn’t actually change. It’s the same object on every onGloballyPositioned
call.boundsInWindow
like this (Case 2)
val boundsInWindow by remember {
derivedStateOf {
println("Recomputing")
coords?.boundsInWindow()
}
}
derivedStateOf
record the reads of the actual fields of LayoutCoordinates
and re-run the computation when they change?boundsInWindow
looks at isn’t “state”. It’s NodeCoordinator._rectCache
which is just a regular MutableRect
efemoney
03/08/2023, 1:20 PMAlexander Maryanovsky
03/08/2023, 1:20 PMefemoney
03/08/2023, 1:23 PMAlexander Maryanovsky
03/08/2023, 1:35 PMcompose.runtime.State
).efemoney
03/08/2023, 1:51 PMAlexander Maryanovsky
03/08/2023, 1:58 PMLoney Chou
03/08/2023, 4:27 PMLayoutCoordinates
are `NodeCoordinator`s which won't change. And since LayoutCoordinates
are not @Stable
, retrieving info from them is not guaranteed to be seen as state reads, so derivedStateOf
won't work. You need Modifier.onPlaced
to reach this goal since it will be called on each placement.Zach Klippenstein (he/him) [MOD]
03/08/2023, 4:55 PMLayoutCoordinates
don’t work with the snapshot state notification system either, although it is that way for a good reason: performance. The easiest way to handle this is to put the entire LayoutCoordinates
object in a mutableStateOf(…, neverEqualsPolicy())
, the key being that neverEqualsPolicy
, so that the object will notify changes every time you write to it. Then your derivedStateOf
should work as expected.
sinceJust to clarify,are notLayoutCoordinates
, retrieving info from them is not seen as state reads@Stable
@Stable
has nothing to do with whether something is or isn’t seen as a state read, or the snapshot system at all. A state read is always seen as a state read, and adding @Stable
to something that’s not backed by snapshot state won’t magically add change notifications. That annotation is only a hint to composable functions about whether they can skip recomposing or not when passed the same object as a parameter.Alexander Maryanovsky
03/08/2023, 5:25 PMLoney Chou
03/09/2023, 12:03 AMLayoutCoordinates
are not `@Stable`", I meant "don't expect derivedStateOf
to rerun if you retrieve info from this class". Because one of the @Stable
requirements is "Changing to public properties should notify composition".