How to remember the scroll position of LazyColumn built with Paging 3
This is going to be a brief post to show to work around a frustrating issue that I found when building this app using Jetpack Compose 1.2.1
and navigation compose version 2.5.0
. It has been amazing working with Jetpack Compose and you can read all about it here.
The issue was that the LazyColumn
could not "remember" its scroll position, which meant going to the start of the list navigating to a details screen and back to the main list. This, as we all know, is not a good user experience, which is why I endeavoured to fix the bug. This is how I came across this issue, which outlines the problem.
The Fix According to this comment, there is a workaround, which looks as follows and has been explained:
@Composable
fun <T : Any> LazyPagingItems<T>.rememberLazyListState(): LazyListState {
// After recreation, LazyPagingItems first return 0 items, then the cached items.
// This behavior/issue is resetting the LazyListState scroll position.
// Below is a workaround. More info: https://issuetracker.google.com/issues/177245496.
return when (itemCount) {
// Return a different LazyListState instance.
0 -> remember(this) { LazyListState(0, 0) }
// Return rememberLazyListState (normal case).
else -> androidx.compose.foundation.lazy.rememberLazyListState()
}
}
So how do you use the above function? As you can see, it is an extension function on type LazyPagingItems<T>
meaning you can use it as follows in the MovieList.kt
file in the app linked.
@Composable
fun MovieList(
movies: Flow<PagingData<Movie>>,
modifier: Modifier,
onMovieClick: (movie: Movie) -> Unit
) {
val lazyMovieItems = movies.collectAsLazyPagingItems()
val listState = lazyMovieItems.rememberLazyListState()
LazyColumn(
verticalArrangement = Arrangement.spacedBy(8.dp),
modifier = modifier,
state = listState
) {
itemsIndexed(lazyMovieItems) { index, movie ->
if (index == 0) {
Spacer(modifier = Modifier.padding(4.dp))
}
MovieItem(
movie = movie!!,
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.clickable { onMovieClick(movie) }
)
}
...
}
}
If this confuses you, then perhaps you can use a different name for your extension function but this will do the trick until Google releases a fix.
Happy coding!
PS Let us connect on Twitter , GitHub , and Linkedin . You can follow me on Hashnode as well and let us build awesome things together.