androidandroid-jetpack-composeandroid-pagingandroid-paging-3android-paging-library

In Compose PagingData / LazyPagingItems returns 0 items and Loading state initially on Config Changes before returning data from cache


So I have this screen:

@Composable
fun ChatDetailsScreen(
    chatDetailsViewModel: ChatDetailsViewModel = hiltViewModel(),
    navigateUp: () -> Unit
) {
    val chatMessagesResponse = chatDetailsViewModel.chatMessagesResponse.collectAsLazyPagingItems()

    val itemsCount = chatMessagesResponse.itemSnapshotList.size
    val loadState = chatMessagesResponse.loadState.refresh

    Timber.d("chatMessagesResponse size $itemsCount, loadStatus: $loadState")

On the first screen opening there are the following logs:

chatMessagesResponse size 0, loadStatus: Loading(endOfPaginationReached=false)
chatMessagesResponse size 0, loadStatus: Loading(endOfPaginationReached=false)
chatMessagesResponse size 15, loadStatus: NotLoading(endOfPaginationReached=false)
chatMessagesResponse size 15, loadStatus: NotLoading(endOfPaginationReached=false)
chatMessagesResponse size 30, loadStatus: NotLoading(endOfPaginationReached=false)

But after config changes (for example orientation changes) there is again size 0 and loadStatus: Loading which causes reseting the scroll in LazyColumn (list size changes from 30 to 0 and then from 0 to 30) and displaying ProgressBar though the data is not being refresh but it's returned from the cache:

chatMessagesResponse size 0, loadStatus: Loading(endOfPaginationReached=false)
chatMessagesResponse size 30, loadStatus: NotLoading(endOfPaginationReached=false)

I expect to see only the second line on config changes but still it gives me size 0 and loadStatus: Loading which breaks everything and it's very annoying...

On config config changes it reuses the cached data that's why I get 30 right away (15 per page) but why do we get 0 before that I don't understand it...

p.s.: ViewModel:

@HiltViewModel
class ChatDetailsViewModel @Inject constructor(
    // ...
) : ViewModel() {
    
    val chatMessagesResponse = getChatMessagesUseCase(chatId)
                                 .cachedIn(viewModelScope)
}

The same issue if you open some other screen and return back to this, you expect to see the same list at the last scroll position but it resets it, also it should not display any progress bar because there was no new request triggered...


Solution

  • Will be fixed soon in new release:

    This has been fixed internally and will be available in the next release. The next release version is TBD.

    In short, the fix works by making cached data immediately available for presentation after config change / navigation. This prevents the list from having zero items right after config change/ navigation so the scroll state will be preserved without requiring workarounds such as comment#24.

    That said, it only works if there is cached data to begin with. This means you need pager.flow.cachedIn(scope). Note that any PagedData mapping/transformations should be applied prior to cachedIn(), such as

    val pager = pager.flow.map { pagingData -> 
        pagingData.map {
           // map items
        }
    }.cachedIn(scope)
    

    https://issuetracker.google.com/issues/177245496#comment45