Android Paging 3 — Fundamental abstractions of pagination.

Understanding fundamentals of Jetpack Paging 3.0 library.

Anand M
4 min readNov 14, 2021
Photo by Giammarco Boscaro on Unsplash

What is Paging 3.0 library ❓

Paging 3.0 is a complete rewrite of older Paging 2.0. When we load the list of paged data in the app, we have to take care of a lot of things, keep track of keys, prevent duplicate data, track UI states (loading, error, refresh, and retry), list transformations, merge data sources, and more. Paging library with its abstractions makes it easy to implement these common operations.

Significant differences from older versions.

  • Asynchronous support using Kotlin Flow, LiveData, RxJava, and Guava.
  • Ensuring efficient usage of system resources with in-memory caching and built-in deduplication.
  • Responsive UI support with built-in load and error states also includes a refresh and retry mechanism.
  • Configurable adapters with scroll listeners for auto data request mechanism, including built-in header, and footer decorators.
Source: Android Developers Blog — Paging 3

The above flow diagram represents how the paging library integrates with each layer of the recommended android app architecture. Referenced from Android Developer Blog — Paging 3.

Step-by-step guide for paging library 🛠️

In this section, we will learn how to use fundamental components of the paging library effortlessly. To keep it simple just to get started with the paging library we will not be using any complex network calls or room DB, instead, we will be using contacts ContentProvider as the data source.

To help us to understand I have created a sample code, following MVVM architecture. Check the repository for the implementation of paging 3.0.

Gradle dependencies

Below is referenced snippet from Android Developers Docs. Add the following dependencies to app-level build.gradle file. Note that you can add only the required dependencies and ignore the rest.

dependencies {
def paging_version = "3.0.1"

implementation "androidx.paging:paging-runtime:$paging_version"

// alternatively - without Android dependencies for tests
testImplementation "androidx.paging:paging-common:$paging_version"

// optional - RxJava2 support
implementation "androidx.paging:paging-rxjava2:$paging_version"

// optional - RxJava3 support
implementation "androidx.paging:paging-rxjava3:$paging_version"

// optional - Guava ListenableFuture support
implementation "androidx.paging:paging-guava:$paging_version"

// optional - Jetpack Compose integration
implementation "androidx.paging:paging-compose:1.0.0-alpha14"
}

Creating a pagination data source

Depending on where we will load data from Paging 3.0 supports two kinds of data sourcing implementations.

  1. PagingSource: is an abstract class to load data from a single source like network, room DB, or any data source supporting pagination.
  2. RemoteMediator: is an abstracted implementation to load data from layered sources, like Network + DB pagination.

Since we are using ContentProvider, which is a single source of paginating data, we will discuss only PagingSource.

PagingSource

To define the data source we will create ContactPagingSource.kt class which implements PagingSource<Int, Value>.

PagingSource accepts two parameters.

  1. Key: defines which data to load next. It can accept Int, representing page number or position, or String if our network uses String values for defining the next token.
  2. Value: type of data loaded by this PagingSource that will be passed to our adapters to display values on the RecyclerView. In our code Value is a data class Contact.kt.

PagingSource provides two public functions to override.

  • load(): is a suspend function to trigger an async load of data from the network, DB, or any source of paginated data. This function holds most of the pagination mechanism.
  • LoadParams: hold on to the params required to load for PagingSource, such as a key for indexing, loadSize defining the number of items per page.
  • LoadingResult: wraps loading states, success or error, current page data, nextKey and prevKey parameters to assist in loading more data.
  • getRefreshKey(): provides the key for the next page load whenever the current PagingSource is invalidated.

Pager and PagingConfig

  • Pager: responsible for initializing Paging and building a reactive stream of PagingData. Provide extension properties to support LiveData, RxJava, and Flow.
  • PagingConfig: configurable object to define loading behavior keys, such as pageSize, maxSize per paged, and initialLoadSize.
  • PagedData: container object holding a snapshot of paginated data from PageSource. This object is passed onto the adapter's submit() function.

Consuming PagedData in ViewModel

After creating Pager in the repository class, the value returned can be consumed as LiveData, Flow, or by RxJava operators. At this stage, we can implement various transformation mechanisms as required.

Passing data to the adapter

  • PagingDataAdapter: this should be implemented by our adapter class. It is a wrapper class implementing RecyclerView.Adapter conveniently handling load state, listening to scroll to fetch more.
  • submitData(): a suspending function to yield PagedData to the adapter.

Conclusion

Paging 3.0 is a major update over previous versions of the paging library. Support for reactive streams Flow, LiveData, and RxJava. The simplest way to handle load states, success or error, handling of load keys, headers, and footers, supporting responsive UI. In-built support for refreshing and retrying, layered data requests.

--

--

Anand M

Android & ML Engineer | GitHub: @anandmali | LinkedIn: /in/anand-m-58917392 | Currently focused on advancing ML capabilities. 🚀