Search code examples
android-pagingandroid-paging-3android-paging-library

Android Paging 3 is not loading next page


After migrating from custom paging implementation to Jetpack Paging 3 library, data is not loading as expected. The first page is handled correctly according to the PagingConfig of Pager:

internal fun createProductListPager(pagingSource: ProductListPagingSource): Pager<Int, Product> = Pager(
    config = PagingConfig(
        pageSize = 10,
        prefetchDistance = 2,
    ),
    initialKey = 0,
) { pagingSource }

Here is an extract of the Adapter:

public class PagingProductCardAdapter(private val viewBinder: CoreViewBinder) :
    PagingDataAdapter<Listable, RecyclerView.ViewHolder>(viewBinder.getDiffUtils()) {


    public val list: List<Listable>
        get() = snapshot().items


    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
      // ...
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        viewBinder.bind(list[position], holder)
    }
    // ...
}

When scrolling to the bottom of the RecyclerView, the next page is not loaded at all (No call to PagingSource.load()) What can go wrong?


Solution

  • How does the PagingSource know when to load more data? What magic is behind it?
    Well, it turns out that the Adapter is responsible for this. How can the Adapter be aware of loaded data? You have to call getItem() as documented:

    /**
     * Returns the presented item at the specified position, notifying Paging of the item access to
     * trigger any loads necessary to fulfill [prefetchDistance][PagingConfig.prefetchDistance].
     *
     * @param position Index of the presented item to return, including placeholders.
     * @return The presented item at [position], `null` if it is a placeholder
     */
    protected fun getItem(@IntRange(from = 0) position: Int) = differ.getItem(position)
    

    As we were accessing the entire list via snapshot:

    public val list: List<Listable>
        get() = snapshot().items
    

    the Adapter couldn't know what item was being loaded, and couldn't trigger the next page loading.

    So the fix is:

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        getItem(position)?.let {
            viewBinder.bind(list[position], holder)
        }
    }
    

    With this, everything is working properly!