Alexander Maryanovsky
03/08/2023, 12:05 PMboundsInWindow is not recomputed when the size changes (🧵)Alexander Maryanovsky
03/08/2023, 12:06 PM@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")
}
}
}
}
}Alexander Maryanovsky
03/08/2023, 12:07 PMLayoutCoordinates object is actually InnerNodeCoordinator which doesn’t actually change. It’s the same object on every onGloballyPositioned call.Alexander Maryanovsky
03/08/2023, 12:09 PMboundsInWindow like this (Case 2)
val boundsInWindow by remember {
derivedStateOf {
println("Recomputing")
coords?.boundsInWindow()
}
}Alexander Maryanovsky
03/08/2023, 12:09 PMderivedStateOf record the reads of the actual fields of LayoutCoordinates and re-run the computation when they change?Alexander Maryanovsky
03/08/2023, 12:14 PMboundsInWindow looks at isn’t “state”. It’s NodeCoordinator._rectCache which is just a regular MutableRectAlexander Maryanovsky
03/08/2023, 12:29 PMefemoney
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).Alexander Maryanovsky
03/08/2023, 1:36 PMefemoney
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".