I'm using Paging 3 with RemoteMediator and Room to display a list of items in a RecyclerView with PagingDataAdapter. We have an issue that causes the list to jump to the beginning when underlying data saved in the database is updated multiple times(in some cases). I've managed to create a reproducible scenario:
- I scroll down to the second page
- I execute an SQL update statement via App inspection, changing a property for one of the displayed items. This triggers PagingSource invalidation. Item UI is correctly updated. The scroll position does not change. Already loaded pages(first page, and pages > 2) seem to be removed from recycler view based on printed logs adapter. Only the currently visible page remains loaded.
Then I do one of the following:
- I immediately execute another SQL update statement, changing a property for any of the displayed items. I would expect the same behavior as for the first update. However, this time the list jumps back to the first page.
- Alternatively, we scroll back to the top of the list. The paging library loads the first page from the database. Then we scroll back down to the second page. We execute the same update statement. This time the scroll position does not change.
It seems that the underlying problem is that the Paging library cannot properly handle additional database invalidation after data has already been invalidated once and all but current pages have been removed from the recycler view.
My questions are:
- Is it expected behavior that after the second update scroll position is not kept? Is this an implementation problem on my side or could it be a bug in the paging library?
- Is it expected behavior that all pages except the current page are removed from RecyclerView even when just one item actually changed? Could this be a "DiffUtil.ItemCallback" problem?
- Would implementing placeholders resolve the problem?
Relevant code from repository:
@MainThread
fun fetchNewData(...): LiveData<PagingData<DisplayCard>> {
val pagingSourceFactory = ... // Get appropriate PagingSource based on some conditions
@OptIn(ExperimentalPagingApi::class)
return Pager(
config = PagingConfig(
pageSize = DiscussionUseCase.PAGE_SIZE, // PAGE_SIZE = 20
prefetchDistance = 2,
enablePlaceholders = false,
initialLoadSize = DiscussionUseCase.PAGE_SIZE // PAGE_SIZE = 20
),
remoteMediator = ItemRemoteMediator(...),
pagingSourceFactory = pagingSourceFactory
).liveData
}
One of Room queries - others are very similar:
@Query("SELECT d.* FROM discussions AS d WHERE ... ORDER BY datetime(d.lastPostDate) DESC")
fun getCardList(query: String?): PagingSource<Int, DisplayCard>
Relevant code from ViewModel:
/**
* MediatorLiveData that is triggered every time one of the filters is changed and we have to fetch fresh data
*/
val listChanges = MediatorLiveData<ListChangesAction>()
val repoResult = listChanges.switchMap {
updateUI()
fetchNewData().cachedIn(viewModelScope)
}
Relevant code from Activity:
viewModel.repoResult.observe(this) { pagingData ->
adapter.submitData(lifecycle, pagingData)
}