Pagination using Paging Library with RxJava and Dagger

Ashwini Kumar
MindOrks
Published in
6 min readJun 15, 2018

--

I talked on the intricacies of MVVM and the Whys & Hows of migration from MVP to MVVM in my previous article.

Refer it here:

Strive for Reactiveness

Until now, you were doing pagination by yourself mainly by the use of RecyclerView onScrollEvent changes. This used to tell when the last visible item is reached and to trigger the API for more data.

private RecyclerView.OnScrollListener scroller = new RecyclerView.OnScrollListener() {
@Override
public void onScrolled(RecyclerView recyclerView, int dx, int scrollDown) {
super.onScrolled(recyclerView, dx, scrollDown);
if (scrollDown > 0) {
loadMoreShows(recyclerView);
}
}
};

When more data was fetched, this was given to the adapter to update the list. This involved a lot of to-and-fro work mainly, handling the scroll event, fetching data and updating the list.

Reasons to change:

  1. While you are always trying hard to make the android app as reactive as possible, the earlier solutions were not upto the mark and lacked the very essence of reactiveness.
  2. Many times, you require that the large data being fetched from the data source is consumed gradually.
  3. Also, Paging Library consumes less network bandwidth and fewer system resources in data requests. Users who have metered or small data plans appreciate such data-conscious apps.
  4. Since the PagedListAdapter is backed by DiffUtil.Callback which uses AsyncPagedListDiffer to calculate the differences, even during data updates and refreshes, the app continues to respond quickly to user input. More on this below.

Let’s see the main components of PagingLibrary using the feature implementation on my TvMaze repository. The new feature will aim to display all the shows present on Shows screen with Pagination and Retry logic using PagingLibrary.

DataSource

This is the component responsible for fetching the data from your backend or database. PagedList is backed by the DataSource. It queries data from its DataSource in response to loading hints. PagedListAdapter calls loadAround(int)to load content as the user scrolls in a RecyclerView. You create the DataSource using the DataSource.Factory. The DataSource should invalidate itself if the snapshot is no longer valid. If a DataSource becomes invalid, the only way to query more data is to create a new DataSource from the Factory. So, I created the ShowsDataSourceFactory like this:

DataSource.Factory class is responsible for providing the DataSource for you. Now the next question is: What kind of DataSource to be created? There are 3 types of DataSource provided for now:

  1. PageKeyedDataSource - To be used when you have next/previous page information(eg.url) to be fetched is coming in the response.
  2. ItemKeyedDataSource - To be used when you need to use N-1 data to fetch item N.
  3. PositionalDataSource - To be used when you have defined set of data i.e. the number of items in the data is fixed (say a large data set) and you want to paginate on that data.

My pagination on TVMaze Shows API is based on page number. That said, ItemKeyedDataSource looks to be the most apt DataSource for my use case.

ItemKeyedDataSource provides 4 methods to be implemented by the class extending it.

The names of the methods are self explanatory. I wanted to show the data from my initial load itself, so I did not use the loadBefore method. Also, since the pagination on TvMaze Shows API is based on the page number itself, I am using page number as key to load next data.

@NonNull
@Override
public Integer getKey(@NonNull Show item) {
return pageNumber;
}

When the list of items is fetched from your source, you will give the data(list of Shows in my case) to the LoadCallback’s onResult method. This internally dispatches result to the receiver which is waiting for the List to be consumed.

@Override
public void onResult(@NonNull List<Value> data) {
if (!mCallbackHelper.dispatchInvalidResultIfInvalid()) {
mCallbackHelper.dispatchResultToReceiver(new PageResult<>(data, 0, 0, 0));
}
}

It’s time to see the receiver now.

PagedListAdapter

It is a wrapper class written around AsyncPagedListDiffer that implements common default behaviour for item counting, and listening to PagedList update callbacks. You will just have to provide the DiffUtil.ItemCallback to create your own PagedListAdapter. It is backed by PagedList<T> . This PagedList will be submitted to the PagedListAdapter when you will receive the PagedList<T>> from your DataSource. Let’s see how I am doing this in TvMaze repository.

I defined the params on the way Pagination needs to be performed using PagedList.Config. LivePagedListBuilder takes the dataSource factory & pagedlist config objects and provides the PagedList. This LiveData<PagedList<Show> is being observed by my AllShowsActivity which submits the list to ShowsPagedAdapter.

public class ShowsPagedAdaptor extends PagedListAdapter<Show, RecyclerView.ViewHolder> {
private final Callback callback;
private Context context;
private NetworkState currentNetworkState;

ShowsPagedAdaptor(@NonNull DiffUtil.ItemCallback<Show> diffCallback, Callback callback) {
super(diffCallback);
this.callback = callback;
}
}

You can view the complete ShowsPagedAdapter here. Now, I wanted to show the ProgressBar when the data is being fetched from the API and the PagingLibrary does not provide any callbacks or methods to be consumed on the UI thread. Also, to handle retry when there is no internet connection while pagination, I wanted to update my adapter by appending an error row at the bottom. So, I created LiveData<NetworkState> for the same. initialLoadStateLiveData will take care of showing loader during initial fetch call and pagedNetworkStateLiveData for network failures during pagination.

For network retry, I saved the last state of DataSource, basically LoadParams and LoadCallback, so that once my adapter gives a call for retry, I can call loadAfter method with the saved states.

@Override
public void loadAfter(@NonNull LoadParams<Integer> params,
@NonNull LoadCallback<Show> callback) {
this.params = params;
this.callback = callback;
Log.d(TAG, "Fetching next page: " + pageNumber);
paginatedNetworkStateLiveData.postValue(NetworkState.builder()
.status(NetworkState.Status.LOADING).build());
Disposable showsDisposable = tvMazeApi.getShows(params.key)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(shows -> onMoreShowsFetched(shows, callback), this::onPaginationError);
compositeDisposable.add(showsDisposable);
}

Check out the sample video here:

If you want to check the complete diff for PagingLibrary implementation, refer here:

PagingLibrary has been developed to ease out the complexities of pagination and make it more reactive while taking care of all the intricate details by itself. It delivers the top notch functionality and is going to become more powerful in the upcoming future.

That’s it for now. I will cover Room library in my next article. So stay tuned! Better yet, check out the complete TvMaze repository to know more about the use of LiveData and ViewModel. TvMaze also has Static Code Analysis using PMD, FindBugs and Checkstyle integrated. Check the article here:

Update 12th July, 2018:

Article on Room integration has been published. Read here:

Update [18th Aug, 2020]

TvFlix repository now uses Paging 3 for pagination of shows on AllShowsActivity Refer below PR to see the full migration changes:

Check out all the top articles at blog.mindorks.com

--

--

Ashwini Kumar
MindOrks

🎯 Engineering towards excellence every single day.