Android Paging Library with Kotlin Coroutines

Harun Wangereka
5 min readJan 27, 2019

--

Update Note

This article uses a version of the Paging Library which is below v3.0. Paging 3.0 is a complete re-write of the library. It uses Kotlin, and it has first-class support for Kotlin coroutines and flow. It also has some new features. For example, it supports headers and footers, and it has retry and refresh mechanisms. Luckily, I have a tutorial on raywenderlich.com on the same. You can have a read here: Paging Library for Android With Kotlin: Creating Infinite Lists. You can still continue reading this article in case you are using a version of the paging library which is below v3.0.

Android Paging Library was introduced last year as part of the Android Jetpack libraries for loading infinite lists. Coroutines are a great way to write asynchronous code that is perfectly readable and maintainable. Whereas there are many articles about how to implement the paging library in android, i did not find one explaining how you can leverage the advantage of Kotlin Coroutines to load infinite lists.

This article assumes you have the basic knowledge of Kotlin and Kotlin Coroutines. For more about coroutines, you can check the official documentation.

Getting Started

We will start by configuring the build.gradle file to be as the snippet below shows

After adding our dependencies we are now good to go. We can now add our paging logic to the app. For this app, we are going to use the Reddit API to show how paging library works with coroutines.

Declaring our Retrofit Interface

The following snippet shows our retrofit interface

You may have noticed that instead of Call<T>, we now have a suspend function in our interface function.

Next, we will create our retrofit client class as follows

Notice we have added the CoroutineCallAdpaterFactory() a Retrofit 2 CallAdapter.Factory for Kotlin coroutine's Deferred.

Choosing and Adding a Data Source

A DataSource is a class that manages the actual loading of data for your list. It provides callbacks that instruct you to load some data at a certain range or with a certain key value. There are three types of DataSource provided by the Paging library:

  • ItemKeyedDataSource
  • PageKeyedDataSource
  • PositionalDataSource

It’s important to choose the data source that best fits your structure. Use:

  • ItemKeyedDataSource if you need to use data from item N to fetch item N+1.
  • PageKeyedDataSource if pages you load embed next/previous keys. For example, if you’re fetching social media posts, you may need to pass a next page key to load the subsequent posts.
  • PositionalDataSource if you need to fetch pages of data from a specific location in your data store.

The Reddit API returns data in such a way that you receive a one-page key that corresponds to a list of Reddit posts. Since you have one key per page of Reddit data, you should use PageKeyedDataSource.This is our PostsDataSource.

Notice instead of the normal retrofit onResponse and onFailure callbacks, we’ve replaced it with scope.launch{} coroutine builder which starts a coroutine in the background and keeps working in the meantime.

In this case, we get the scope in our PostDataSource Class constructor.

Since our fetchPosts function is a suspend function, we can call await on the response and get rid of the retrofit callbacks successfully.

When the response is successful, you extract the RedditPost objects from the API response and pass them through to the callback object supplied to you. If you didn’t receive any RedditPosts from the API, simply pass in an empty list. You also pass through both the before and after keys that the Reddit API gave you.

Using a PagedListAdapter

Our adapter extends the PagedListAdapter() class and has two parameters. The first type parameter is RedditPost. That’s the model class this adapter will work with and the class the RedditDataSource produces. The second type parameter is the ViewHolder, just like a normal RecyclerView.Adapter. You also need a DiffUtilclass.DiffUtil is a utility class that helps to streamline the process of sending a new list to a RecyclerView.Adapter. It can generate callbacks that communicate with the notifyItemChanged or notifyItemInserted methods of the adapter to update its items in an efficient manner, without you having to deal with that complex logic.

That is the DiffUtilCallBack class with two method: areItemsTheSame and areContentsTheSame. The areItemsTheSame asks whether two items represent the same object. The areContentsTheSame asks whether the content of the same item has changed.

Setting up the View Model

ViewModel will be responsible for creating the paged list along with its configurations. PagedList is a wrapper list that holds your data items and invokes the DataSource to load the elements. It typically consists of a background executor (which fetches the data) and the foreground executor (which updates the UI with the data).

A PageList takes in the following parameters:

a. setEnablePlaceholders(enablePlaceholders :Boolean) — Enabling placeholders to mean there is a placeholder that is visible to the user till the data is fully loaded.
b. setInitialLoadSizeHint(initialLoadSizeHint :Int ) — The number of items to load initially.
c. setPageSize(pageSize:Int) — The number of items to load in the PagedList.
d. setPrefetchDistance(prefetchDistance:Int) — The number of preloads that occur. For instance, if we set this to 10, it will fetch the first 10 pages initially when the screen loads.

Below is our view model with the above configurations

We have the initializedPagedListBuilder which fetches the pagedlist from our data source. In our ViewModel also we pass the viewModelScope to the PostsDataSource factory.

Finally, the wrap-up

This is our activity code after doing all the above steps

As you can see, our activity has little code since as we have placed all the logic in our view model and data source. The activity displays the list of Reddit posts from the ViewModel.

For the full project, you can find it on GitHub

A big thank you to Michael Bukachi for doing a Pull Request and refactoring from GlobalScope.launch{} implementation to using viewModelScope in the data source and the ViewModel which is the recommended way of handling coroutines instead of having them launch globally.

For suggestions and improvements, let me know in the comment section.

--

--

Harun Wangereka
Harun Wangereka

Written by Harun Wangereka

Google Developer Expert for Android | Android Engineer | Co-organizer droidconKE, Android254 & Kotlin Kenya | Android Author @raywenderlich.com

Responses (3)