Compose LazyColumn key, messes up scrolling when sorting the items
Asked Answered
H

3

3

I'm trying to implement simple sort with items that are in a state.

This will work perfectly, only problem is that the animation is now gone, because it doesn't have a key.

LazyColumn(
    state = listState,
) {
    items(items.size) { index ->
        val ticker = items[index]
        Card(
            modifier = Modifier
                .fillMaxWidth()
                .animateItemPlacement(), // This won't work
        ) {
            Item(...)
        }
    }
}

But If I try to change it to:

items(items.size, key = { items[it].name }) { index ->

or If I use:

items(items, key = { items[it].name }) { item ->

It will have animations, but it will move items above and below current scroll position (even if you don't move). The wanted result is that you stay on top of the items, because we didn't move.

This is in a viewModel:

private val _state = MutableStateFlow<Response<List<Item>>>(Response.Loading)
val state get() = _state
private fun updateList() {
    viewModelScope.launch(Dispatchers.IO) {
        client.getItems()
            .onSuccess {
                unFilteredList = it.toMutableList()

                val filteredList = filterList()
                _tickerListState.value = Response.Success(filteredList)
            }
            .onFailure {}
    }
}
Haupt answered 4/11, 2022 at 17:16 Comment(0)
C
3

I've added an empty item at the top to workaround this behavior as suggested by this comment in the Google issue tracker.

Also I'm scrolling to the top with a LaunchedEffect when the sorting criteria change.

LazyColumn(state = rememberLazyListState()) {
    item(key = "0") {
        Spacer(Modifier.height(12.dp)) // I need to add some padding so I use the empty item for this 
    }
    items(items) {
        ...
    }
}
Clance answered 2/2, 2023 at 16:10 Comment(0)
H
1

Semi-Fixed the problem with:

LaunchedEffect(items.first()) {
    listState.animateScrollToItem(0)
}
LazyColumn{
        items(items, key = { it.id}) { item-> ...
}

Sometimes the animation skips, but it works 90% of the time. If I remove the animateScrollToItem, the animation looks better, but then there is a scroll position issue.

Edit: Because I had search I had to improve the logic:

LaunchedEffect(items.firstOrNull()) {
    if (items.isNotEmpty() && searchQuery.isEmpty()) {
        listState.animateScrollToItem(0)
    }
}

This now works like it should in the beginning.

Haupt answered 5/11, 2022 at 10:20 Comment(0)
A
0

... only problem is that the animation is now gone, because it doesn't have a key.

I created a sample alphabet letter sorting/filtering and yes, without an item key the animation stops working. Though I'm not sure why in your case you said having this makes the animation work, it doesn't work on mine however.

items(items) { item ->

But anyway, I'll try to make assumptions, so please bare with me.

If you think of it, without animation, any changes made on the data structure is immediately rendered to the UI, it will just simply display the new data structure, but if you add some delay/duration(animation) to those rendering changes, say sorting (e.g. switching the first and last item position):

  • Fade out last positioned item and Fade it in to first position
  • Fade out first positioned item and Fade it in to last position

the framework has to have something that can uniquely identify those position(e.g. LazyColumn's item key) so it won't mess up while doing the animation rendering, also maybe to avoid performing some overhead just to keep track of the changes.

And as per the API documentation, it needs a key for it to animate

enter image description here

The wanted result is that you stay on top of the items, because we didn't move.

Not sure if I understand this correctly, but if you want to stay on the first item, you can utilize a LaunchedEffect and use the list's size as its key to execute scrolling operations to the LazyColumn every filtering.

LaunchedEffect(key1 = list.size) {
    lazyListState.animateScrollToItem(0) // scrolls to first item
}
Autophyte answered 5/11, 2022 at 0:18 Comment(3)
Thank you for the answer. Sorry for the misunderstanding, You are correct, I added key to items(items) to make an animation. Now for the issue at hand. We don't solve it with LaunchedEffect(list.size), because if you are sorting the list, size can be the same and it won't have any effect. I'm wondering why does the scroll issue happen, because it shouldn't scroll anywhere when you are animating items with .animateItemPlacement. To sum: If you don't use the keys it doesn't mess up the scroll position, but if you use items with keys, it will mess up the scroll position.Haupt
I can only make an honest guess but "scrolling to a position" + "items with keys" shouldnt have anything against each other, i have many implementations where both exists and I have no problem with the two. There must be something in your code that causes the "scroll issue"Autophyte
Yes of course. If the first item is the same after sorting, then there is no problem. But if the first item changes, the scroll is focused on that item and not on new first item. I'll try with LaunchedEffect(list first item changes){ scrollToPosition}. This Works!Haupt

© 2022 - 2024 — McMap. All rights reserved.