In a better attempt to understand the confluence o...
# compose
t
In a better attempt to understand the confluence of remember keys and derivedState, I put together the following:
Copy code
var base by remember { mutableIntStateOf(10) }
var a by remember(base) { mutableIntStateOf(base * 2) }
var b by remember(base) { mutableIntStateOf(base * 3) }
val c = remember(a, b) { (a * b) }
val d1 by remember { derivedStateOf { (a * b) } }
val d2 by remember(base) { derivedStateOf { (a * b) } }
I've read and recognize that c would be the better case for the product of a and b. I wanted to play with the derived case because sometimes nested mutable states makes the derived state attractive because of its ability to "auto observe" the less direct references. In the above example, c, d1, and d2 update fine as long as only a and b are change. But if new mutable states are created because the base is changed, then new a/b mutableStates are created. d2 continues to work as expected. And d1 does not. Which I understand (if you skip the
by
delegates, and type out all of the .value extensions for a and b, it's a little clearer why in my opinion). Because the original derivedState references the original mutableStates, it has no ability to know that it needs to be updated. It's auto observed values are stale. Here's what surprised me though. If I change that d1 line to this:
Copy code
val d1 by remember { derivedStateOf { 
    println("base reference $base")
    (a * b) } }
That didn't work either. I can see that it's indeed rerunning the computation when base changes. But it's fetches of a and b don't seem to update. Why is that?
b
Because it is capturing the old a & b state objects, when base changes a & b are and reallocated but the tracking in d1 is still pointing to the old reference
I wrote about the derivedStateOf in this blog, it might clear it up for you https://medium.com/androiddevelopers/jetpack-compose-when-should-i-use-derivedstateof-63ce7954c11b
t
I'll have a read. I thought I had concocted a case in the past where I basically created something like
Copy code
class Person {
   var name by mutableStateOf<String>("foo)
}

var persons by mutableStateOf(listOf(... multiplePerson objects ...))
and then had a list ui for them where I had
Copy code
val sortedPersons by remember { 
   derivedStateOf { 
      persons.sortedBy { each -> each.name } } }
And I could add/remove new people from the list (as long as I updated the persons with the modified list) OR I could change their names, and the sorting worked fine. IIRC, I did not have to use any remember keys for that to work correctly. So in that case, as persons were added, it seemed to update the nested observation of the names. Did I confuse myself?
(at the time, i thought this was the coolest dang thang in the world; i've been doing UIs since the early days of Smalltalk-80 MVC and the wiring dependencies, especially when you have to observe nested properties of dynamically managed containers, was/is always the biggest pain in all of these systems)
b
That doesn't sound like it would work to me... In general you don't want to be nesting mutable objects. There is another good article about that https://dev.to/zachklipp/two-mutables-dont-make-a-right-2kgp
t
The TLDR or that post is: Don't put mutable collections inside mutable state holders. That's not what I'm describing.
b
Fair enough, but in general nesting mutable states is not advised
s
To make it clearer, consider removing
by
for
a
and
b
and then printing out states directly. You'll see that: • d1 captures
MutableState(...)@1
and
MutableState(...)@2
, • when you change
base
, it creates
MutableState(...)@3
and
MutableState(...)@4
, which is read by
d2
, but never read in
d1
, as the
calculation
lambda you provided to the derived state never updated.
z
Just to clarify, nesting mutable snapshot states is fine for correctness, we do that all the time. It’s good to be aware that snapshot state reads have some cost, so nesting too deeply can be inefficient. Mutable non-snapshot states are the problem.
I think you might actually be hitting a bug in nested derived states that was recently reported.
s
I don't think that's a bug, unless i am missing something you need to re-create derived states if you use new state instances
z
Yup, nvm
b
TIL you can nest mutable snapshot state and it works though. That's pretty mind blowing
s
We have different definitions of "works" in this case :D
z
Also worth noting that for this code snippet, this is all more complicated than it needs to be. Most simple value transformations can just be done directly, without remember or derived state. But I understand you’re exploring. Just wanted to add the disclaimer lol
The state invalidations will happen correctly, but it might not be the most performant. For most app code, the performance hit probably won’t be significant I wouldn’t think.