https://kotlinlang.org logo
i

Issa

03/07/2021, 11:56 AM
While taking the
Pathway
first code lab
Compose Basics
section
Animating your list
I have noticed that
LazyColumn
doesn’t remember the
isSelected
state of the item when recomposing. I have copy pasted the code from the
Pathway
and got the same results. Just when you scroll until the item is no more visible, when you scroll back the
isSelected
state is lost. IMO this is wrong cuz the state should be cached anyway and when recomposing the compiler should consider the previous state. Should I
Hoist
the state of the
Greeting
widget so I can manage it in the
LazyColumn
scope or this is just a Compose
Bug
?
🤔 Thanks in advance!
j

jim

03/07/2021, 12:11 PM
Hoisting state is never a bad idea 😉
i

Issa

03/07/2021, 12:16 PM
Well that’s not efficient in my opinion 😕 even though it seems like a better idea however, a map is needed to sync the state of the items and their
isSelected
state -> added complexity
@jim So here is what I did, I hoisted the state to the caller scope
NamesList
in this case. Now
LazyColumn
is not recomposing the items if the
state
changes, It only does when the item is being visible on the screen again.
Copy code
@Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {
    val selectedStates = remember { mutableStateOf(mutableMapOf<Int, Boolean>()) }
    val map = mutableMapOf<Int, Boolean>()
    names.forEachIndexed { index, _ ->
        map[index] = false
    }
    selectedStates.value = map
    LazyColumn(modifier = modifier) {
        itemsIndexed(items = names) { index, name ->
            val i: Boolean = selectedStates.value[index] ?: false
            Greeting(
                name = name,
                isSelected = i,
                onSelected = {
                    selectedStates.value[index] = !it
                }
            )
            Divider(color = Color.Black)
        }
    }
}
j

jim

03/07/2021, 12:59 PM
Sorry the API not very discoverable. You'd want to use
mutableStateMapOf
instead of
mutableMapOf
, else Compose won't know when the map is mutated.
1
i

Issa

03/07/2021, 1:31 PM
@jim Thanks! that did the trick 😉 As far as I understand
remember{}
remembers the state for only one composition invocation, however not
recomposition
done by the
LazyFoo
apis, cuz these guys they unsubscribe from the previous and init a new invocation which yields a fresh state. So far here is the code if you want to update the
Pathway
code lab.
Copy code
@Composable
fun NameList(names: List<String>, modifier: Modifier = Modifier) {

    val selectedStates = remember {
        mutableStateMapOf<Int, Boolean>().apply {
            names.mapIndexed { index, _ ->
                index to false
            }.toMap().also {
                putAll(it)
            }
        }
    }

    LazyColumn(modifier = modifier) {
        itemsIndexed(items = names) { index, name ->
            Greeting(
                name = name,
                isSelected = selectedStates[index] == true,
                onSelected = {
                    selectedStates[index] = !it
                }
            )
            Divider(color = Color.Black)
        }
    }
}
Thanks again for your help 😉
j

Jorkoh

03/07/2021, 2:03 PM
It's called the "Compose basics" codelab because it's about the basics, the "Using State in Compose" delves into the hoisting pattern also with selected stuff in a lazyColumn. Anyway you don't even need a map, just a list of the selected items or their ids
👆 1
👍 1
a

Andrey Kulikov

03/07/2021, 2:08 PM
also if you will use
rememberSaveable
instead of
remember
the state would survive the item being scrolled off and on
🤔 1
i

Issa

03/07/2021, 5:56 PM
I will give
rememberSaveable
api a try this sounds really interesting!! @Andrey Kulikov
@Andrey Kulikov That works like a charm! Although reading the docs of
rememberSaveable()
Copy code
It behaves similarly to remember, but the stored value will survive the activity or process recreation using the saved instance state mechanism (for example it happens when the screen is rotated in the Android application).
I am wondering if abusing this patter with say 1000 items in a list where each item is using
rememberSaveable
under the hood, I am wondering if this has any performance penalties? 🤔 Other than that… I loved the idea and it was exactly what I was looking for! Thanks again!
I also noticed that it doesn’t work in Interactive Preview mode but maybe this is something to be fixed in the future versions of compose!
a

Andrey Kulikov

03/07/2021, 6:33 PM
yes, this value will also be restored when the activity would be recreated. no performance penalty, but a bit of an extra memory to be kept, like 1000 booleans in this case
👌 1
but only if you will really scroll over 1000 items
i

Issa

03/07/2021, 7:46 PM
That’s interesting, I wonder what would be the solution of implementing a viewHolder for the list, that’s something i will look into, maybe there is a way to achieve something similar to recyclerView.
a

Andrey Kulikov

03/07/2021, 7:52 PM
Sorry, what do you mean? Why do you need a ViewHolder?
i

Issa

03/08/2021, 10:10 AM
In real world cases, the state of an item is not just
isSelected
state, it might be more complex depending on the UI. So I thought maybe by creating a
ViewHolder
like API, it would be easier to hold the state. I am not sure if this is the responsibility of the item itself or the
LazyColumn
4 Views