I'm using Paging 3 with RemoteMediator that shows cached data while fetching new data from the network.
When I refresh my PagingDataAdapter
(by calling refresh()
on it) I want my RecyclerView to scroll to the top after the refresh is done. In the codelabs they try to handle this via the loadStateFlow
the following way:
lifecycleScope.launch {
adapter.loadStateFlow
// Only emit when REFRESH LoadState for RemoteMediator changes.
.distinctUntilChangedBy { it.refresh }
// Only react to cases where Remote REFRESH completes i.e., NotLoading.
.filter { it.refresh is LoadState.NotLoading }
.collect { binding.list.scrollToPosition(0) }
}
This indeed does scroll up, but before DiffUtil has finished. This means that if there is actually new data inserted at the top, the RecyclerView will not scroll all the way up.
I know that RecyclerView adapters have an AdapterDataObserver
callback where we can get notified when DiffUtil has finished diffing. But this will cause all kinds of race conditions with PREPEND
and APPEND
loading states of the adapter which also cause DiffUtil to run (but here we don't want to scroll to the top).
One solution that would work would be to pass PagingData.empty()
to the PagingDataAdapter
and rerun the same query (just calling refresh
won't work because the PagingData
is now empty and there is nothing to refresh) but I would prefer to keep my old data visible until I know that refresh actually succeeded.
In cases, such as searching a static content, we can return false
inside areItemsTheSame
of DiffUtil.ItemCallback
as a workaround. I use this also for changing sorting property.
@Florian I can confirm we don't need the postDelayed
to scroll to top using version 3.1.0-alpha03
released on 21/07/2021.
Also, I managed to make further filter the loadStateFlow
collection so it doesn't prevent StateRestorationPolicy.PREVENT_WHEN_EMPTY
to work based on @Alexandr answer. My solution is:
By the time I am writing this, the latest version of Paging3 is 3.1.0-alpha03
so import:
androidx.paging:paging-runtime-ktx:3.1.0-alpha03
Then set the restoration policy of your adapter as following:
adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY
If you have compilation error for the above mentioned change, make sure you are using at least version 1.2.0-alpha02 of RecyclerView. Any version above that is also good:
androidx.recyclerview:recyclerview:1.2.0-alpha02
Then use the filtered loadStateFlow
to scroll the list to top only when you refresh the page and items are prepended in the list:
viewLifecycleOwner.lifecycleScope.launch {
challengesAdapter.loadStateFlow
.distinctUntilChanged { old, new ->
old.mediator?.prepend?.endOfPaginationReached.isTrue() ==
new.mediator?.prepend?.endOfPaginationReached.isTrue() }
.filter { it.refresh is LoadState.NotLoading && it.prepend.endOfPaginationReached && !it.append.endOfPaginationReached}
.collect {
mBinding.fragmentChallengesByLocationList.scrollToPosition(0)
}
}
The GitHub discussion can be found here: https://github.com/googlecodelabs/android-paging/issues/149
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With