How to use Jetpack Pagination 2 library for a modern search and pagination interface on Android

Tolga Can Ünal
Hipo
Published in
4 min readJan 17, 2020

--

Every year, new and better approaches are available to Android and Kotlin developers. Last week, I needed to create a search mechanism that also supported pagination. As I was looking around the previous implementations I’ve used, all of them looked outdated. And also, the conferences that I’ve attended and blog posts I read were encouraging me enough to use the Pagination library available in Jetpack. In this article, I’ll walk through how I went about implement this solution.

First, let’s add the Pagination library to our project. I’ve used version 2.1.1:

implementation "androidx.paging:paging-runtime:${paging}"

Edit Text is needed to get the user query. We need to observe query changes on the ViewModel side. I’ve created ConflatedBroadcastChannel to pass these changes. Every text change will offer new query to the channel.

We’re currently passing every text change to channel. Let’s use this channel. Previously, we were using Observable to handle this case but now as we’re in 2020, let’s try Flow. debounce function of RxJava was helping us to not DDOS our own server (!) and it’s great that Flow provides this function too.

Don’t worry, I didn’t forget to fill inside of onEach function. I’ll return to this after implementing pagination. Pagination library needs DataSource to give us what we need. There’re three choices ItemKeyedDataSource, PageKeyedDataSource and PositionalDataSource. The API that I’m dealing with provides next url. So, the example below will include the implementation of PageKeyedDataSource one. Choose the one suits to your case. (btw the implementations of other ones are nearly same.)

Use PageKeyedDataSource if pages you load embed keys for loading adjacent pages. For example a network response that returns some items, and a next/previous page links.

Use ItemKeyedDataSource if you need to use data from item N-1 to load item N. For example, if requesting the backend for the next comments in the list requires the ID or timestamp of the most recent loaded comment, or if querying the next users from a name-sorted database query requires the name and unique ID of the previous.

Use PositionalDataSource if you can load pages of a requested size at arbitrary positions, and provide a fixed item count. PositionalDataSource supports querying pages at arbitrary positions, so can provide data to PagedLists in arbitrary order. Note that PositionalDataSource is required to respect page size for efficient tiling. If you want to override page size (e.g. when network page size constraints are only known at runtime), use one of the other DataSource classes.

After creation of DataSource, we can begin to create PagedList. The code below will create that wrapped with live data to be observed in SearchActivity/Fragment. You can change PageSize, PrefetchDistance and other configurations according to your need while creating config variable.

Android Jetpack brought a new adapter which is PagedListAdapter. Only way to handle PagedList easily is to use this adapter. So, we will use RecyclerView and let me give you an example of how PagedListAdapter works. You need DIFF_CALLBACK to handle modifications of the items.

After creating your adapter and connecting with RecyclerView, we can start to observe PagedList live data in your SearchActivity/Fragment. The example below shows the observation on the SearchFragment side but can be used in SearchActivity with changing viewLifecycleOwner to `this`.

We currently linked our PagedList to our QueryAdapter but query change doesn’t change the data. It just shows the results without any query parameter. We need to change our Flow observation a little in that point. Every query change should invalidate available/obsolete list .

All in all, we can say that every text change goes to ViewModel through a channel and the channel handles and warns DataSource if needed to invalidate its data. If there’s new data needed, DataSource pushes the data to PagedList.

Handling Error, Empty and Loading States

Now, we can say that our search is working but we did not handle possible error cases, loading and empty state yet. I’ve created a model named PaginationStatus which is a sealed class and includes Loading, Empty, NotEmpty and Error. You can add any object you want according to your requirements.

We’ll use PaginationStatus for alerting user to what the state is right now. There’ll be one more live data needed in SearchViewModel to transfer object to SearchActivity/Fragment. The live data will provide us what’s the status of pagination currently.

// Add this to SearchViewModel
val paginationStatusLiveData = MutableLiveData<PaginationStatus>()

After that, the only thing needed is to observe this live data in SearchActivity/Fragment and handle according to object type.

Thanks for reading.

Keep an eye on posts from Hipo by subscribing to our newsletter and following us on Twitter.

--

--