Why A works and B doesn't? Isn't `remember` just t...
# compose
t
Why A works and B doesn't? Isn't
remember
just to keep the value across recompositions?
mutableStateOf
is not enough to trigger recompositions?
Copy code
@Composable
@Preview
fun Test() {
    var a by remember { mutableStateOf(false) }
    var b by mutableStateOf(false)
    val c by derivedStateOf { a && b }
    Column {
        Text(text = "A $a")
        Text(text = "B $b")
        Text(text = "C $c")
        Button(onClick = { a = !a }) {
            Text("A")
        }
        Button(onClick = { b = !b }) {
            Text("B")
        }
    }
}
😐 1
👀 1
m
Every time the Test function is recomposed (re-executed)
b
will be recreated from scratch
You need
remember{}
to stop that happening and ensure the object persists even across re-executions of the function. Yeah it's a bit weird. Think of the function as if it's a class and
remember{}
as if it's a property, if that helps. Then you can see that
b
will be just a normal local variable.
t
But
b
is a object that keep the value, no? So if I do
b = !b
why it goes back to false? I don't think the entire screen is being recreated, no?
k
Calling
b = !b
, will recompose
Test()
again. This is because the
Column
is inlined, so that the recomposition scope goes up to
Test
itself. And calling
Test
itself will create a new member variable
b
with
false
value
m
"Composition" is a fancy term for re-execution. Every time anything changes in your UI, conceptually, the entire code from
application{}
or
setContent{}
on down is re-executed fresh. The only way to preserve data is either use global variables or use
remember
.
b
is not an object that keeps its value. A
MutableState
is just an observable variable with fancy extras. It can trigger event handlers when it changes, but it has nothing to say about how long it lasts in the heap. It's no different to allocating any other object as a local variable.
t
I thought when you changed
b
it would just recompose what's using the value of
b
🤔
k
Because that’s true.
the entire code from
application{}
or
setContent{}
on down is re-executed fresh.
is wrong statement in that regards and would be super duper inefficient to have in any ui framework
t
Ok, so it's as I imagined, the
Test()
is not recomposed, so why would the value be lost? That's the bit I'm missing
k
In your example, like I said, your Test would be recomposed as whole, because the the
b
value is read in a scope of it
If you do this:
Copy code
Column {
        Text(text = "A $a")
//        Text(text = "B $b")
        Text(text = "C $c")
        Button(onClick = { a = !a }) {
            Text("A")
        }
        Button(onClick = { b = !b }) {
            Text("B ${b}") // note here
        }
    }
Then your
b
will keep the value, until
a
is changed.
that’s because of recomposition scope, where
b
is no longer read within
Test
scope, but rather in Button’s
content
scope.
a
is still read within
Test
scope, so if that changes, compose calls
Test
function again
t
So basically anytime a
State
changes, Compose will retrigger everything from the earliest access point of the
State
?
k
Yes, exactly. The point to know is that Compose function creates recomposition scope, so if any Snapshot value (which is data holder created by
mutableStateOf
) is read within that scope, compose will re-run that composable if that value changes
I can suggest this article by Vinay, which is great in explaining this part https://www.jetpackcompose.app/articles/donut-hole-skipping-in-jetpack-compose
t
Thanks @krzysztof super helpful. I'll read the article 😄
k
you’re welcome, good luck!
m
Yes, that's why I said "conceptually". It'll only re-execute parts, but that's an optimization.