Home > Net >  Compose: Why does a list initiated with "remember" trigger differently to Snapshot
Compose: Why does a list initiated with "remember" trigger differently to Snapshot

Time:05-16

I've been messing around with Jetpack Compose and currently looking at different ways of creating/managing/updating State.

The full code I'm referencing is on my github

I have made a list a piece of state 3 different ways and noticed differences in behavior. When the first list button is pressed, it causes all 3 buttons to be recomposed. When either of the other 2 lists are clicked though they log that the list has changed size, update their UI but trigger no recompose of the buttons ?

To clarify my question, why is that when I press the button for the firsList I get the following log messages, along with size updates:

Drawing first DO list button
Drawing List button
Drawing second DO list button
Drawing List button
Drawing third DO list button
Drawing List button

But when I press the buttons for the other 2 lists I only get the size update log messages ?
Size of list is now: 2
Size of list is now: 2

var firstList by remember{mutableStateOf(listOf("a"))}
val secondList: SnapshotStateList<String> = remember{ mutableStateListOf("a") }
val thirdList: MutableList<String> = remember{mutableStateListOf("a")}

Row(...) {
        println("Drawing first DO list button")
        ListButton(list = firstList){
            firstList = firstList.plus("b")
        }
        println("Drawing second DO list button")
        ListButton(list = secondList){
            secondList.add("b")
        }
        println("Drawing third DO list button")
        ListButton(list = thirdList){
            thirdList.add("b")
        }
    }

When I click the button, it adds to the list and displays a value. I log what is being re-composed to help see what is happening.

@Composable
fun ListButton(modifier: Modifier = Modifier,list: List<String>, add: () -> Unit) {
    println("Drawing List button")
    Button(...,
        onClick = {
            add()
            println("Size of list is now: ${list.size}")
        }) {
        Column(...) {
            Text(text = "List button !")
            Text(text = AllAboutStateUtil.alphabet[list.size-1])
        }
    }
}

I'd appreciate if someone could point me at the right area to look so I can understand this. Thank you for taking the time.

CodePudding user response:

I'm no expert (Well,), but this clearly related to the mutability of the lists in concern. You see, Kotlin treats mutable and immutable lists differently (the reason why ListOf<T> offers no add/delete methods), which means they fundamentally differ in their functionality.

In your first case, your are using the immutable listOf(), which once created, cannot be modified. So, the plus must technically be creating a new list under the hood.

Now, since you are declaring the immutable list in the scope of the parent Composable, when you call plus on it, a new list is created, triggering recompositions in the entire Composable. This is because, as mentioned earlier, you are reading the variable inside the parent Composable's scope, which makes Compose figure that the entire Composable needs to reflect changes in that list object. Hence, the recompositions.

On the other hand, the type of list you use in the other two approaches is a SnapshotStateList<T>, specifically designed for list operations in Compose. Now, when you call its add, or other methods that alter its contents, a new object is not created, but a recomposition signal is sent out (this is not literal, just a way for you to understand). The way internals of recomposition work, SnapshotStateList<T> is designed to only trigger recompositions when an actual content-altering operation takes place, AND when some Composable is reading it's content. Hence, the only place where it triggered a recomposition was the list button that was reading the list size, for logging purposes.

In short, first approach triggers complete recompositions since it uses an immutable list which is re-created upon modification and hence the entire Composable is notified that something it is reading has changed. On the other hand, the other two approaches use the "correct" type of lists, making them behave as expected, i.e., only the direct readers of their CONTENT are notified, and that too, when the content (elements of the list) actually changes.

Clear?

  • Related