Angus L
07/15/2022, 9:46 AMLaunchedEffect(someMutableState.value)
will cause a custom Layout()
in the same @Composable
function to remeasure every time the value of the key changes. Which was not what I was expecting.
This got me wondering if there is something wrong with the way LaunchedEffect
is used in this case and what the ideal approach to get around this remeasure would be.
Wrapping it into a separate @Composable
or SubcomposeLayout
would work, but that feels even worse to me.Stylianos Gakis
07/15/2022, 10:20 AMLayout
also recomposes since it’s inline and therefore has the same recomposition scope as MinimalRemeasureExample
.
When you do not access the .value of counter
in that scope, and you only access it inside the layout(constai…)
part, you’re not invalidating the outer recomposition scope since you’re only reading that state in the placement (I think) step of composition.Angus L
07/15/2022, 10:29 AMStylianos Gakis
07/15/2022, 10:34 AMMinimalRemeasureExample
recomposition scope here which is the same recomposition scope for Layout
therefore the entire thing is recomposing.
Here’s a sample of introducing a new Composable which will not be inline therefore have its own recomposition scope, while also deferring reading the value by using a lambda and only reading it in the layout()
block, therefore not recomposing the entire Layout
composable.
The output of the above snippet reads:
> Task :run
Measuring 0
Placing 0
Counter changed.
Placing 0
Counter changed.
Placing 0
Counter changed.
Angus L
07/15/2022, 10:37 AMStylianos Gakis
07/15/2022, 10:41 AMBoxWithConstraints
therefore introducing a composable which uses SubcomposeLayout
feels like a super overkill for something like this. Especially considering all the advice to avoid SubcomposeLayout
unless absolutely needed.
Also just to answer this
regardless of if the value is actually used inside that particular layout, which still feels weird to me.That’s the thing, since you are calling .value on it for LaunchedEffect you are using it. THe fact that you’re not using it for something that emits some UI doesn’t change the fact that you are reading that state in that scope. So I don’t think this is something that should feel weird to you.
@Composable
fun NewRecompositionScope(content: @Composable () -> Unit) {
content()
}
And wrap either your MyLayout
or the LaunchedEffect
, maybe like this
NewRecompositionScope {
LaunchedEffect(counter) {
println("Counter changed.")
}
}
And it will work.
And hopefully if someone else has a smarter idea they can contribute it in this discussion 😄Angus L
07/15/2022, 10:48 AMStylianos Gakis
07/15/2022, 11:06 AMcontent()
since otherwise LaunchedEffect
could never be called in the first placeAngus L
07/15/2022, 11:19 AMStylianos Gakis
07/15/2022, 11:25 AMLayout
with the NewRecompositionScope
as that is practically what we want to do, introduce a recomposition scope for the Layout, not for LaunchedEffect
. Then you’re free to add whatever other composable in that scope.
I understand that this feels sub-optimal, I am still hoping that someone can come in with a better solution to this if it exists.Angus L
07/15/2022, 11:34 AMTo avoid the problem with if you want to add another text for example what I'd do is wrap the Layout with the NewRecompositionScope as that is practically what we want to do
This seems to behave differently by the way.
If I understood correctly what you meant, wrapping the layout does not seem to avoid the remeasure.
NewRecompositionScope {
Layout(
Probably because LaunchedEffect still sits on top in the "hierarchy", so only pushing LaunchedEffect(The Receiver) further down avoids it?Stylianos Gakis
07/15/2022, 12:03 PMAngus L
07/15/2022, 12:15 PMnatario1
07/15/2022, 12:24 PMAngus L
07/15/2022, 12:39 PMStylianos Gakis
07/15/2022, 12:42 PMZach Klippenstein (he/him) [MOD]
07/15/2022, 8:50 PM