I'm currently integrating the Paging 3
library into my Android application, utilizing a RecyclerView
to display data. While the basic implementation is in place, I'm encountering an unexpected behavior related to scrolling
after loading the next page of data. Specifically, the RecyclerView
is automatically scrolling to the top position after loading new data, which is not the intended behavior.
The setup involves using the PagingDataAdapter
along with the RecyclerView
as per Paging 3's guidelines
. Pagination
is working correctly, but the automatic scroll
to the top after fetching and loading subsequent pages is proving to be problematic. The goal is to have users continue viewing their current position without being abruptly taken to the top of the list whenever new data is loaded.
The relevant portion of my code resembles the following:
mSSSPagination(
searcher = productSearcher,
pagingConfig = PagingConfig(pageSize = 20, enablePlaceholders = false),
transformer = {
it.deserialize(ProductAlgolia.serializer())
}
)
paginator?.liveData?.observe(viewLifecycleOwner) { pagingData ->
plpAdapter?.submitData(lifecycle, pagingData)
}
Paging Source Code Snippet
class SSSSearcherPagingSource<T : Any>(
private val searcher: SearcherForHits<out SearchParameters>,
private val transformer: (ResponseSearch.Hit) -> T
) : PagingSource<Int, T>() {
override fun getRefreshKey(state: PagingState<Int, T>): Int {
return 0 // on refresh (for new query), start from the first page (number zero)
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, T> {
return try {
val pageNumber = params.key ?: 0
searcher.query.page = pageNumber
searcher.query.hitsPerPage = 20
val response = search() ?: return emptyPage()
val data = response.hits.map(transformer)
val nextKey = if (pageNumber < response.nbPages) pageNumber + 1 else null
LoadResult.Page(
data = data,
prevKey = null, // no paging backward
nextKey = nextKey
)
} catch (exception: Exception) {
Log.w("Paging search operation failed", exception)
LoadResult.Error(exception)
}
}
Pagination Issue Video
Update 1
Paginator Code
public fun <T : Any> Paginator(
searcher: SearcherForHits<out SearchParameters>,
pagingConfig: PagingConfig = PagingConfig(pageSize = 10),
transformer: (ResponseSearch.Hit) -> T
): Paginator<T> = SearcherPaginator(
pagingConfig = pagingConfig,
pagingSourceFactory = { SearcherPagingSource(searcher, transformer) }
)
/**
* A cold Flow of [PagingData], emits new instances of [PagingData] once they become invalidated.
*/
public val <T : Any> Paginator<T>.flow: Flow<PagingData<T>> get() = pager.flow
/**
* A LiveData of [PagingData], which mirrors the stream provided by [flow], but exposes it as a LiveData.
*/
public val <T : Any> Paginator<T>.liveData: LiveData<PagingData<T>> get() = pager.liveData
Diff Util Code Snippet
object ProductDiffUtil : DiffUtil.ItemCallback<ProductAlgolia>() {
override fun areItemsTheSame(oldItem: ProductAlgolia, newItem: ProductAlgolia): Boolean {
return oldItem.objectID == newItem.objectID
}
override fun areContentsTheSame(oldItem: ProductAlgolia, newItem: ProductAlgolia): Boolean {
return oldItem == newItem
}
}
Update 2
Load State Collection Snippet
private fun setLoadingStates() {
viewLifecycleOwner.lifecycleScope.launchWhenResumed {
plpAdapter?.loadStateFlow?.collectLatest { loadStates ->
//Log.d("loadState", loadStates.toString())
Log.d("productList", plpAdapter?.getProductAlgoliaList().toString())
when (loadStates.refresh) {
!is LoadState.Loading -> {
val productList = plpAdapter?.getProductList() ?: arrayListOf()
if (productList.isNotEmpty()) {
if (!isFromSearch) {
createCategoryTabs(productList, selectedCategoryId)
}
trackAlgoliaPLPView()
initMostUsedFilter()
trackMoengagePLPViewEvent(productList = productList)
} else {
noDataFound()
}
if (isFromSearch) {
val suggestion =
algoliaViewModel.suggestions.value?.peekContent()?.second
val query = algoliaViewModel.suggestions.value?.peekContent()?.first
trackMoEngageSearchEvent(
selectedQuery = suggestion?.query,
searchText = query,
productList.isEmpty()
)
}
hideShimmer()
}
is LoadState.Error -> {
hideShimmer()
}
else -> {
}
}
}
}
}
RecyclerView
<com.cooltechworks.views.shimmer.ShimmerRecyclerView
android:id="@+id/plpRecyclerView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:clipToPadding="false"
android:fastScrollEnabled="true"
android:overScrollMode="never"
android:paddingStart="@dimen/dimen_10dp"
android:paddingTop="@dimen/dimen_10dp"
android:paddingEnd="@dimen/dimen_10dp"
android:paddingBottom="@dimen/dimen_55dp"
android:splitMotionEvents="false"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
app:shimmer_demo_layout="@layout/plp_placeholder"
app:shimmer_demo_layout_manager_type="grid"
app:shimmer_demo_shimmer_color="#21ffffff"
tools:visibility="invisible" />
Note: I have observed that while loading the next page Paging adapter position start from 0
For example
Page 0 -> 20 products
page 1 -> loading next 20 products -> position start from 0 (Unexpected)
Finally, I found the solution, I am using one shimmer library which is causing the issue the problem is that when I am hiding the shimmer one method which is getting called causing the issue
binding.plpRecyclerView.hideShimmerAdapter()
This method is replacing the shimmer adapter with the actual adapter
Library Code Snippet
public void hideShimmerAdapter() {
mCanScroll = true;
setLayoutManager(mActualLayoutManager);
setAdapter(mActualAdapter);
}
public void setLayoutManager(LayoutManager manager) {
if (manager == null) {
mActualLayoutManager = null;
} else if (manager != mShimmerLayoutManager) {
mActualLayoutManager = manager;
}
super.setLayoutManager(manager);
}
I removed that library and manage shimmer by my own instead of using library