Stop Guessing When to Restore Scroll Position in Compose

Restoring a scroll position in Jetpack Compose looks simple at first.
val gridState = rememberLazyStaggeredGridState(
initialFirstVisibleItemIndex = savedIndex,
initialFirstVisibleItemScrollOffset = savedOffset
)
However, this approach only works if the items have already been laid out. When your data is loaded asynchronously, the grid often starts with zero items, so the initial scroll position is ignored.
Many developers try to solve this by calling scrollToItem() inside a LaunchedEffect, but there’s another problem:
LaunchedEffect(Unit) {
gridState.scrollToItem(savedIndex, savedOffset)
}
If the layout hasn’t finished yet, the scroll request may run too early.
Wait Until the Grid Is Actually Ready
Instead of guessing when the layout has completed, wait until the grid reports that enough items have been laid out.
LaunchedEffect(Unit) {
val savedPosition = viewModel.savedScrollPosition
// Wait until the layout contains the target item.
snapshotFlow { gridState.layoutInfo.totalItemsCount }
.first { it > savedPosition.index }
gridState.scrollToItem( savedPosition.index, savedPosition.offset )
}
This code suspends until layoutInfo.totalItemsCount becomes greater than the saved index. Only then does it restore the scroll position.
Why layoutInfo.totalItemsCount?
A common alternative is to wait for the data itself.
snapshotFlow { uiState.items.size }
While this tells you the data has arrived, it doesn’t guarantee that the LazyVerticalStaggeredGrid has finished measuring and laying out those items.
layoutInfo.totalItemsCount represents something different: the number of items currently recognized by the lazy layout. Once this value is large enough, the target item is available for scrolling.
In other words:
| State | Meaning |
|---|---|
| uiState.items.size | The data is available. |
| gridState.layoutInfo.totalItemsCount | The grid has laid out the items. |
For scroll restoration, the second condition is the one that matters.
Final Thoughts
When working with lazy layouts, the question isn’t “Has my data loaded?”
The real question is:
“Has the layout become scrollable yet?”
Waiting on layoutInfo.totalItemsCount aligns your restoration logic with the UI’s actual state, making scroll restoration significantly more reliable than relying on data loading alone.