Status Update
Comments
ma...@google.com <ma...@google.com> #2
Hey! Thanks for filing this.
optional lambda to extract key or similar might work, agree. We can even ommit index here and just make it (T) -> Any
.
Anyway, I think there's a way to make it work without new API at all. What if you just set you own key right after ours, since we call item's lambda directly after they key -- I would expect that to work. Not sure it's a sustainable way to do things. (Leland wdyt?)
So maybe it makes sense to allow you to just
LazyColumnFor(items) { item ->
key(item.myKey) {
Text(item.title)
}
}
Ccing Leland and Andrey for more context
ma...@google.com <ma...@google.com> #3
Spoke to Andrey offline, he has some ideas, but we need to prototype it and implement.
Thanks for your feature request once again :)
ma...@gmail.com <ma...@gmail.com> #4
Hi, sorry for not responding earlier. I saw that the experimental LazyColumn
& LazyRow
composables have been added and it looks like it should solve the issue. Will Lazy{Column,Row}
composables be used inside Lazy{Column,Row}For
in the future? Or will Lazy{Column,Row}For
be deprecated?
an...@google.com <an...@google.com> #5
ma...@gmail.com <ma...@gmail.com> #6
As far as I have seen Lazy{Column,Row}
don't set the key anywhere, so it is possible to set a custom key manually. Or am I missing something?
ap...@google.com <ap...@google.com> #7
Branch: androidx-main
commit aafeb1b7807ed6a353896480d25c60d18e85475b
Author: Andrey Kulikov <andreykulikov@google.com>
Date: Wed Jan 27 18:09:39 2021
Add custom keys support for LazyColumn/LazyRow
It allows smarter reordering as we match elements by key, not by index.
Example: we had elements 0, 1, 2 and all of the elements did have some local state with use of remember {}. Without custom keys when we remove an element 1 the list is now 0, 2. But the LazyColumn operates with indexes in the list, so the remembered values which were previously associated with item 1 will now be given for the item 2.
But if we provide custom keys like this:
LazyColumn {
items(list, key = { it }) { ... }
}
We can match the composition state of the item content by these keys, not indexes. This means in the example above the state associated with 2 would still be used even when we removed the item 1. The same would happen if we just reorder items.
We will also use this key as a key for saved instance state mechanism which means that when you use rememberSaveable { } inside the item's content it would be automatically restored when the activity would be recreated after screen rotation of if you navigate back to the screen with the list when using the compose-navigation library. The downside of this is that you have to only use types which can be stored inside the android.os.Bundle on Android (On Desktop there are no restrictions). But what if your custom class is not supported by Bundle? Always try to use the whole item object as a key but something like an id of it. In most cases you have String/Long/Int id which represents the element so pass it as a key:
LazyColumn {
items(users, key = { user ->
}
Relnote: "Custom keys support for LazyColumn/LazyRow was added. This allows us to smarter handle items reordering. So the state you stored in remember {} blocks will move together with the item when you reorder elements or removed the item from the middle.
LazyColumn {
items(users, key = { user ->
}"
Fixes: 164901852
Test: LazyCustomKeysTest, LazyItemStateRestoration
Change-Id: Ia50ef7cd8f47ab87d76edc4e0691199ce426729d
M compose/foundation/foundation/api/current.txt
M compose/foundation/foundation/api/public_plus_experimental_current.txt
M compose/foundation/foundation/api/restricted_current.txt
M compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
A compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyCustomKeysTest.kt
M compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyItemStateRestoration.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/CachingItemContentFactory.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyDsl.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyFor.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyList.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemInfo.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItem.kt
M compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyMeasuredItemProvider.kt
M compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/SaveableStateHolder.kt
Description
Issue
Currently, each child of the wrapped with a
LazyColumnFor
composable is implicitlykey
containing the index of the item. Because of this if the order of the items is changed, the children, that got reordered, are needlessly recomposed (this is because the value of the keys have changed). In the extreme case, when an item is inserted at the beginning of the list, the entire subtree ofLazyColumnFor
get recomposed.Motivation
Oftentimes the items that are rendered in these lazy composables, have unique ids. Thus, it would be useful if there was a way of specifying the id of the item as a key.
Possible solution
I have thought of several solutions:
Option 1 would function similarly to how it does in React. Not specifying a key could be a warning.
Personally, I like option 2 better. I am not sure how this parameter could be named, but its type could simply be:
(Int, T) -> Any
Int
is the index of the itemT
is the type of the itemThe default value for the parameter could be defined as
{ i, _ -> i }
. This way, when the parameter is not specifiedLazyColumnFor
(orLazyRowFor
) would function exactly as it does now.Also maybe a better type signature could be
T.(Int) -> Any
, this way if the user wants to use an id of the item as a key, the function would be as simple as{ id }
instead of{ _, item -> item.id }
.