What would be the best way to react to `mutableSta...
# compose
k
What would be the best way to react to
mutableStateListOf
changes in effects? Say, I have
Parent
which has state
remember { mutableStateListOf<Int>() }
which can be changed by one of its children.
Parent
also has an effect (let’s say
DisposableEffect
) reading changes to the list, like
DisposableEffect(list){/* ... */}
. But that effect won’t be called more than once, because
list
won’t change instance (
SnapshotStateList
), even if items are added.
s
You could: 1- Pass a parameter down the tree
onListChanged: () -> Unit
to let the children notify the parent when it modifies the list; 2- Use
list.size
as key instead of
size
, however I'm not sure how it would react when swapping two elements; 3- Depends on your use case
s
You could use
snapshotFlow()
. It should emit when the list is updated.
☝🏻 1
z
Yep this is precisely what
snapshotFlow
is for. Or any of the lower-level apis for snapshot observation.
k
so having this in
Copy code
val list = remember { mutableStateListOf<Int>() }

  LaunchedEffect(Unit) {
    snapshotFlow { list }.collect {
      Log.i("XXX", "snapshot updated!")
    }
  }
and then calling
Copy code
list.add(2)
should print
snapshot updated!
, correct?
z
I think so. I can't remember off the top of my head if the flow will dedup emits because the list identity is the same - but if you move the log call into the snapshotFlow block and just use an empty collect it should definitely do that.
k
That was my first thought too about
snapshotFlow { list }
, to be updated whenever the list is modified. But it’s not the case here. If I do
snapshotFlow { list.size }
then it’s working as expected. Like you said,
list
has the same identity (due to remember)
z
Right, so a cool trick about snapshotFlow is that you don't actually have to use it as a flow. You can just use it to create a restartable function by doing all your work in the snapshotFlow lambda and just ignoring the emitted values with an empty collect.
👍 1
k
Cool, nice trick 😄
Copy code
LaunchedEffect(Unit) {
    snapshotFlow { Log.i("XXX", "list size: ${list.size}") }.collect {}
  }
My main problem is that, I pass my
mutableListStateOf
down to children (as List<T>) and in there, I have
LaunchEffect(list)
call, which is not re-executed when list is updated.I’ll have to find different way then
z
It would be helpful to know more about your code to figure out what the best overall solution is, but you actually still want to pass the list as a key (since if the identity of the list changes, you need to restart the effect), but you probably want to combine them:
Copy code
@Composable fun Foo(list: List<T>) {
Copy code
LaunchedEffect(list) {
    snapshotFlow { list.… }.collect()
  }
}
(FYI There’s a collect override that doesn’t take any params that’s good for this use case)
🙌 1
k
I have
DisposableEffect
that should listen for changes in
list
, and that effect does not own coroutine scope, so it’s bit tricker
(FYI There’s a collect override that doesn’t take any params that’s good for this use case)
Yup, found it, has to be exported as it’s extension 👌
z
So just change it to a LaunchedEffect
k
Can’t really, there needs to be some cleanup done when
list
changes 😄
z
You can do that with a launched effect. Anything you can do with disposable effect you can do with a LaunchedEffect
k
Do you mind explaining this/show an example?
DisposableEffect has
onDisposed
called, so my guess for LaunchedEffect would be doing a cleanup when its coroutine is cancelled?
z
Exactly
👌 1
246 Views