Android Compose LazyList - keep scrolling position when new items are added to the top of the list

Viewed 956

Here's the original list before I add any new item

Original list

When I add a new item to the end of the list, without scrolling, the first item still remains in that position

List after adding new items to the end

But when I add new items to the start of the list, all the old items is moved down. Notice that New item 0 is not at the very top of the list anymore

List after adding new items to the start

How can I make it so that if I add a new item to the start of the list, the old item still remain in the exact position. And these new items will only be visible when the user scrolls up, not pushing everything down. If I remember correctly, it's the default behavior with RecyclerView

Here's my code: MainActivity.kt

class MainActivity : ComponentActivity() {
    private val list = MutableStateFlow<List<String>>(emptyList())

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContent {
            EmptyComposeTheme {
                val items = list.collectAsState()
                Box(Modifier.fillMaxSize()) {
                    LazyColumn(
                        Modifier.fillMaxSize(),
                    ) {
                        itemsIndexed(items.value) { index: Int, item: String ->
                            Text(item, color = MaterialTheme.colors.onSurface)
                        }
                    }
                    FloatingActionButton(onClick = {
                        val oldList = list.value.toMutableList()
                        oldList.add("New item ${oldList.size}")
                        list.value = oldList
                    }, Modifier.align(Alignment.BottomStart)) {
                       Text("Click me!")
                    }
                    FloatingActionButton(onClick = {
                        val oldList = list.value.toMutableList()
                        oldList.add(0, "New item reversed ${oldList.size}")
                        list.value = oldList
                    }, Modifier.align(Alignment.BottomEnd)) {
                        Text("Click other me!")
                    }
                }
            }
        }
    }
}

I'm using Compose version 1.0.1

3 Answers

Basically you aim at controlling the scroll position as described in some more detail here

While taking your code as a basis, you could hence do the following:

val items = list.collectAsState()
val listState = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()

Box(Modifier.fillMaxSize()) {
    LazyColumn(
        state = listState,
        modifier = Modifier.fillMaxSize(),
    ) {
        ...
    }
    
    ...
    
    FloatingActionButton(onClick = {
        val currentIndex = listState.firstVisibleItemIndex + 1
        val oldList = list.value.toMutableList()
        oldList.add(0, "New item reversed ${oldList.size}")
        list.value = oldList

        coroutineScope.launch {
            listState.scrollToItem(currentIndex)
        }
    }, Modifier.align(Alignment.BottomEnd)) {
        Text("Click other me!")
    }
}

LazyComun with itemsIndexedand index in the key param solved my problem.

LazyColumn(
    state = listState
) {
    itemsIndexed(list, key = {index, item->
       index + item.hashcode() // Including index in the key param solved my problem
    }) {
       ....
    }
}

u can add key param to LazyColumn like this, but if ur firstVisibleIndex = 0, then problem not solved, i do not know why its:

LazyColumn(
    state = listState,
    modifier = Modifier.fillMaxSize(),
    key = { item -> item.id} // or any unique value
) {
    ...
}
Related