Solution for Network-Database (Room) Paging with Paging library, LiveData and Coroutine

Why did I write this article?

Unfortunately, it was not that easy. Paging library documentation has not been entirely clear about how to utilize the library for my purpose. Then I jumped to multiple articles around the Internet and found many solutions for customizing datasource, loading data from a local database (room) only or loading from Network only. I wondered how I could solve my issue. The library supposes to be my ultimate solution.

I missed one thing in my journey.

I acknowledged that many people had the problem of understanding and using the library all around the Internet. As a return to everything I have learned from the past from you guys (yes, I am talking about you), I wrote 2 articles to share my knowledge about how to properly use Paging library with the support of LiveData and Coroutine:

How to implement Network-Database (Room) structure list with Paging library from Jetpack and Coroutine.

Pulling data from ROOM local database:

In this article, we utilize Reddit API so you could the object pretty familiar if you are a Redditor. We will discuss indexInResponse later.

To be able to access to Room database, we must have a DAO interface for our object with add, get, and delete function.

We are going to manage post’s data by its sub so get and delete methods must have subName as their params.

Next, we need to apply Paging library into our code then we can pull local data from ROOM database.

Feeding DataSource.Factory of ROOM into LivePagedListBuilder then you get the above LiveData. (See more configuration here.)

loadPostsFromSubName return a DataSource.Factory reference to a query in ROOM local database.

ViewModel observes the LiveData and feeds its result to recyclerView.

Whenever local data inside the scope of loadPostsFromSubName’s query get updated, that data is streamed up to the RecyclerView.

We just finished a set up for Paging library to deal with getting data out of local database. Great, let’s move to Network next.

Pulling data from Network with coroutine.

  • Pulling the first page of data.
  • Pulling the next page of data.

I use the RedditAPI.kt of google sample so you could check the original file here. Let me save you one click:

The interface offers us 3 API:

  • getTop: to get the top posts based on the input amount of a sub reddit.
  • getTopAfter: to get top posts based on the input amount of a sub reddit but after a post id.
  • getTopBefore: to get top posts based on the input amount of a sub reddit but before a post id. (We don’t cover this one here)

We are going to use getTop and getTopAfter in this article.

For using getTop:

private suspend fun loadFromZero(subName: String, loadSize: Int) =
redditAPI.getTop(subreddit = subName, limit = loadSize)
.data
.children
.map {
it
.data
}

For using getTopAfter:

private suspend fun loadMorePosts(
subName: String,
after: String,
loadSize: Int
) = redditAPI.getTopAfter(subreddit = subName, after = after, limit = loadSize)
.data
.children
.map {
it
.data
}

How can we connect our network call with the flow above?

Paging library offers us a callback interface called BoundaryCallback with 3 methods:

  • onItemAtEndLoaded(T itemAtEnd): is triggered when local datasource reach to its last item. itemAtEnd is the last item.
  • onItemAtFrontLoaded(T itemAtFront): is triggered when local datasource reach to its first item. itemAtFront is the first item.
  • onZeroItemsLoaded(): is triggered when local datasource returns nothing at the first load (similar to refresh case)

We are going to utilize the onZeroItemsLoaded() callback method for the case that when user open the app for the first time.

Let implement BoundaryCallback interface:

Phew, a lot of code. Let’s discuss the class’s arguments:

subName: represent a sub name in Reddit

All network requests in our project use coroutine to handle threading. CoroutineContext is passed from viewModel, which could be tied to the viewModel’s lifecycle and also provides flexibility to cancel the process at any time.

networkPageSize: is the number of items is fetched for each network call.

onZeroLoad(String, Int) : List<Post>? method is for making a network API call that request the first page of a subreddit.

onLoadMore(String, String, Int) : List<Post>? method is for making a network API call that requests one next page of a subreddit

handleResponse(String, List<Post>) method is for process the return from network call and stores data into the local database.

loadMoreState: MutableLiveData<State>

The loadMoreState LiveData is for streaming back the network calls status to viewModel.

Let’s connect them together.

Photo by Alexandre Debiève on Unsplash

We have done with pulling data from the local database and Network. How could we connect them and pass them up to viewModel for using

In the google example, it has an interesting class that I think could be applied for almost all cases in using LiveData with Repository pattern(local database/network).

The code above is my simplify version to use coroutine instead of RxJava in google’s sample. You can check out the original file here.

The PagingPosts object is built by:

  • data = postLiveData : is the LiveData of PagedList<Post> of Paging library that points to a dataset in ROOM local database.
  • networkState = boundaryCallback.networkState: is the network states when boundarycallback trigger its process to load first page or next page of data.

loadPostBySubName is called from viewModel where all LiveData will be passed up to View to be observed.

and observing all LiveData and also kick off the whole process from View (Fragment)

That it is. I skip the UI set up for the PostFeedAdapter because it is quite easy, and I don’t want to waste your time. Check out the project here in Github.

Conclusion.

Dad, Husband, Android Developer, Manga/Video Game Lover, Unity3d learner.