I just stumbled over an interesting lazy list beha...
# compose
s
I just stumbled over an interesting lazy list behavior. Turns out the item composables are actually cached and recycled to a certain degree? See demo code in comments
Demo code:
Copy code
@Composable
fun ListTest() {
    var listData by remember { mutableStateOf((0..10).toList()) }
    Column {
        Button(onClick = {
            listData = listData.toMutableList().apply {
                removeAt(0)
                add(last()+1)
            }
        }) {
            Text(text = "Modify list")
        }

        LazyRow(
            contentPadding = PaddingValues(10.dp),
            horizontalArrangement = Arrangement.spacedBy(10.dp)
        ) {
            items(listData) {
                ListItem(value = it)
            }
        }
    }
}

@Composable
fun ListItem(value: Int) {
    val rememberedItem by remember { mutableStateOf(value) }
    Text(
        modifier = Modifier
            .height(50.dp)
            .width(300.dp)
            .background(Color.LightGray),
        text = "Item $value, remembered: $rememberedItem"
    )
}
Above code shows a list with numbers from 0 to 10. a click on the button removes the first item in the list and adds a new one at the end. So first the list contains numbers from 0 to 10, then 1 to 11 etc
The
ListItem
remembers that value that it shows and then prints the current as well as the remembered values in a text
When running this, i first see
Item 0, remembered: 0
for the first item, but after one button click it changes to
Item 1, remembered: 0
scrolling the list back and forth until it recomposed the items, fixes the issue
my question is, can i control this caching in any way? I would want the caching to go with the value. E.g. the composable that was at second position with value
1
moves to the first position with the same value after the data change. Is that possible?
btw, i understand that using a key for the
remember
would fix the issue as well, but that is not the solution i'm looking for
j
You can surround the individual
items
content with
key(it) { .. }
1
All of Compose is cached/recycled positionally by default, not just a lazy list
i
That's exactly why
items
has a
key
lambda: https://developer.android.com/reference/kotlin/androidx/compose/foundation/lazy/package-summary#(androidx.compose.founda[…]nction1,kotlin.Function2)
a factory of stable and unique keys representing the item. Using the same key for multiple items in the list is not allowed. Type of the key should be saveable via Bundle on Android. If null is passed the position in the list will represent the key.
The default is null, which doesn't sound correct if you want your state to move between positions
s
oh nice, yes using the key lambda should solve it then.
they
key(it)
method is very interesting too. i was not aware of this and can use it in other places
thank you both a lot for the answer
c
I'm not adding much to the conversation here, but just anecdotally I think I remember back in dev preview of compose, LazyColumn didn't actually do any sort of "recycling" and then recycling was actually added later in the alphas/betas. which i just thought was a cool little FYI
s
It's definitely good to know. One of my main arguments for compose was that we don't have to deal with item recycling anymore and now it turns out that there is still some recycling going on. it's easy enough to handle, but you just need to know about it
c
Interesting. I feel like if you model your data correctly, you don't have to worry about recycling. i.e. IMO People typically got bit by recyclerView in view-land because they relied on saving state in views themselves (think a list of checkboxes). But with compose, since most composables out of the box encourage you to hoist state up, it doesn't matter if you throw it into a Column or a LazyColumn.
☝🏻 1
z
Another feature related to this is the relatively new support for “item type” in lazy lists. I believe items are only recycled for use with other items of the same type.
And i believe the caching goes deeper than the compositions as well – i believe we try to re-use the underlying layout nodes as well when possible.
c
Yeah, maybe that's whats happening in this bug I filed recently: https://issuetracker.google.com/issues/221561999
s
Yes i'm sure hoisting the state or moving it into a proper VM would solve the issue as well. We'll have to check if that is possible in our specific case.