I know when we need to remember something that doe...
# compose
s
I know when we need to remember something that doesn’t need to then be mutated, we can simply wrap it in a remember without the mutableStateOf, like
val item = remember(someKey) { ExpensiveConstructedThing() }
instead of
val item by remember(someKey) { mutableStateOf(ExpensiveConstructedThing()) }
However after reading about this very informative article and some official docs, to my understanding when we use
by
on the state object, we get the neat effect of not reading the value at that moment, but deferring the read to whenever we actually need it. Which sometimes comes with the nice side-effect or reading that state object in a “smaller” recomposition scope meaning that when it changes we don’t invalidate the wider recomposition scope that the above definition may be in. So if we do not do this, but use simply
val item = remember(someKey) { ExpensiveConstructedThing() }
do we lose this nice side effect? When
someKey
does change, and the result of
item
changes with it, will it invalidate the recomposition scope in which it is declared in as opposed to where it is actually read?
👀 2
a
The effect only happens when the change happens in the same
State
instance. With
val item by remember(someKey) { mutableStateOf(ExpensiveConstructedThing()) }
you won’t get the effect anyway because the
State
instance itself changed.
k
I’d suggest to read the next chapter of Zach’s article, here: https://dev.to/zachklipp/remember-mutablestateof-a-cheat-sheet-10ma It’d explain the
by
here (property delegation) more clearly. TL;DR: it’s the same as using
state.value
s
Yeah I have read it, and I understand that it’s “just” a
state.value
but the important bit is that this read is deferred to when you actually use the value for the first time instead of on the declaration line, which means that when reading it you are likely to be in a smaller recomposition scope therefore invalidating only that instead of the entire thing.
k
👍 It’d be the same case in both examples, as you “read” the state value by calling
myState.value
if not using delegate, while with delegate you just call
value
s
Albert let me try and understand. Is the State instance different since the remember key would be changed therefore it creates a whole new State instance with the new call to mutableStateOf()?
a
Exactly.
s
Awesome, thank you! So in this case, even if I declare this line at the top of my composable but I then read it inside a smaller recomp scope, it would still invalidate the entire scope no matter what I do right?
👍 1
a
Yep.
🙌 1
s
And Krzysztof, I don’t know if I don’t understand you or if you don’t understand me, but I think we’re not referring to the same thing. I was referring to in which recomposition scope a state value is read, you’re just describing the difference between using a delegate and not using it. Your “TL;DR: it’s the same as using `state.value`” is only half true as it has implications on how often things recompose. Which is also why using the destructuring approach is not equivalent to using the delegate approach. Not about correctness, but performance.
k
Yeah, I misunderstood totally what you meant, that’s on me 😅
is only half true as it has implications on how often things recompose.
I’d be interested in seeing an example of it - you say using delegate approach would trigger less recompositions than property access approach?
It looks like the delegating extension function on State does nothing special than just calling
value
on receiver, hence I said it’s the same thing
s
Yes, this article called donut-hole skipping happens to explain how recomposition scopes work and how reading a State object inside a smaller scope invalidates only that scope and not the scope in which the variable was declared in. I don’t have a working sample but if you change the
val counter by remember { mutableStateOf(0) }
to
val (counter, setCounter) = remember { mutableStateOf(0) }
then it will invalidate the scope in which this line is declared on, not just in which it is read in. With all that said, now that I see what you said again better, if you do use just
val counter: State<Int> = remember { mutableStateof(0) }
and you read it with counter.value in the same scope, then it is equivalent yes. When you were saying = and by are the same thing I was immediately thinking of using the destructuring declaration, as I’ve almost never seen someone use the
=
syntax and keeping a hold of a
State
object, since that’s imo the least convenient of all the approaches 😄
👍 1
k
haha, that make a lot of sense now 😄 Yes, in case of descructuring, it make sense that state value is read immediately in line it’s being declared in, so it’s wider scope that’s get recomposed
s
Yeah I am glad we understood each other, sorry for making this wrong assumption before 🤗
🙌 1
z
The only “reads” that compose cares about, or even knows about, are reads of snapshot state objects. Those are the only reads that can invalidate composition. If you instantiate a class with some non-snapshot-state-backed properties, then read those properties, that will never invalidate the composition.
🙌 1