Paging Library for Android With Kotlin: Creating Infinite Lists
In this tutorial, you’ll build a simple Reddit clone that loads pages of information gradually into an infinite list using Paging 3.0 and Room. By Harun Wangereka.
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Sign up/Sign in
With a free Kodeco account you can download source code, track your progress, bookmark, personalise your learner profile and more!
Create accountAlready a member of Kodeco? Sign in
Contents
Paging Library for Android With Kotlin: Creating Infinite Lists
30 mins
- Getting Started
- Defining a PagingSource
- Fetching Reddit Posts From the Network
- Fetching Posts From the PagingSource
- Configuring Your ViewModel to Fetch Data
- Using a PagingData Adapter
- Displaying Data in Your UI
- Enabling Key Reuse
- Displaying the Loading State
- Breaking Free of Network Dependency
- Creating a RemoteMediator
- Adding RemoteMediator to Your Repository
- Fetching Previous and Next Pages With RemoteMediator
- Paging 3.0 Remarks
- Where to Go From Here?
Configuring Your ViewModel to Fetch Data
Navigate to the ui/RedditViewModel.kt. Add the following to class:
// 1
private val redditRepo = RedditRepo(application)
// 2
fun fetchPosts(): Flow<PagingData<RedditPost>> {
return redditRepo.fetchPosts().cachedIn(viewModelScope)
}
Here you’re:
- Creating an instance of
RedditRepo. You’ll use it to fetch data from the Reddit API. - Calling
fetchPosts, which you created inRedditRepoin the previous section. You use thecachedIncall to cache the data in a scope. In this case, you’re usingviewModelScope.
Great! Now your ViewModel is ready to provide data to the view. :]
In the next sections, you’re going to add the logic to display this data to the user.
Using a PagingData Adapter
Open ui/RedditAdapter.kt. You’ll see that RedditAdapter extends PagingDataAdapter. The first type parameter is RedditPost. That’s the model this adapter uses, which is the same RedditPagingSource class you created earlier produces. The second type parameter is RedditViewHolder, as in RecyclerView.Adapter.
In order to handle logic around updating the list, you’ll need make use of DiffUtil class. DiffUtil is a utility class which streamlines the process of sending a new list to RecyclerView.Adapter. It has callbacks that communicate with the adapter’s notifyItemChanged and notifyItemInserted methods to update its items efficiently. Great news — this means you don’t have to deal with that complex logic!
A util class named DiffUtilCallBack is already created that extends from DiffUtil.ItemCallback and it overrides the below two methods:
- areItemsTheSame Check whether two items represent the same, equal object. This is usually determined by comparing the unique IDs of the two items.
-
areContentsTheSame Check whether the content of the item are the same. You call
areContentsTheSameonly ifareItemsTheSamereturnstrue.
If the adapter determines that the content has changed for the item at position in the list, then it re-renders the item.
Next, while inside the ui/RedditAdapter.kt, replace the TODO() inside onBindViewHolder() with below:
getItem(position)?.let { redditPost ->
holder.bindPost(redditPost)
}
Here notice that you are using the getItem method provided by PagingDataAdapter to get RedditPost. After getting the item, you call bindPost(redditPost) with the post at that position. This method handles displaying the data for a post in a single RecyclerView item.
Now your RedditAdapter is ready! Next, you’ll use this adapter to display the Reddit posts in the UI.
Displaying Data in Your UI
Navigate to ui/RedditPostsActivity.kt. Add the following code to class:
private val redditViewModel: RedditViewModel by lazy {
ViewModelProvider(this).get(RedditViewModel::class.java)
}
Here you’re initializing RedditViewModel using lazy keyword, which means the initialization will occur only after the first call. Then consecutive calls will return the same instance of the ViewModel.
Next, add this method at the bottom of RedditPostsActivity, below the setupViews method:
private fun fetchPosts() {
lifecycleScope.launch {
redditViewModel.fetchPosts().collectLatest { pagingData ->
redditAdapter.submitData(pagingData)
}
}
}
This code fetches the posts from RedditViewModel. Since the ViewModel returns a Flow, you use collectLatest to access the stream. Once you have the results, you send the list to the adapter by calling submitData.
In order to wire everything up, you need to head over to the onCreate() replace //TODO: Replace with fetchPosts() with below:
fetchPosts()
All done! Now, build and run. You’ll see a screen that looks like below (Of course, your content will be different):
Woohoo! You have your list! Try scrolling down, and you’ll see that new content loads as you approach the bottom of the list.
Enabling Key Reuse
If you continue scrolling, you’ll notice that the app crashes. Take a look at your logcat:
Reddit API reuses the keys in some instances to fetch the posts. Unfortunately, PagingSource does not support this behavior.
To solve this issue, navigate to RedditPagingSource.kt. Add the following code to the class:
override val keyReuseSupported: Boolean = true
keyReuseSupported defaults to false. Here you’re overriding the default setting to true. This enables PagingSource to reuse keys in fetching the posts.
Build and run. Now everything works as it should. :]
Wow, that took quite a few steps, but now you’re able to fetch an infinite list of items from the network!
Next, you’ll add a loading header and footer to show the user the status of the load.
Displaying the Loading State
In this section, you’re going to add a ProgressBar while you’re fetching new items after you reach the end of a page. You’ll also display an error message to the user in case it fails.
First, open ui/RedditLoadingAdapter.kt. Note how RedditLoadingAdapter extends LoadStateAdapter. LoadStateAdapter is a special list adapter that has the loading state of the PagingSource. You can use it with a RecyclerView to present the loading state on the screen.
Replace the //TODO: not implemented inside LoadingStateViewHolder with below:
// 1
private val tvErrorMessage: TextView = itemView.tvErrorMessage
private val progressBar: ProgressBar = itemView.progress_bar
private val btnRetry: Button = itemView.btnRetry
// 2
init {
btnRetry.setOnClickListener {
retry()
}
}
// 3
fun bindState(loadState: LoadState) {
if (loadState is LoadState.Error) {
tvErrorMessage.text = loadState.error.localizedMessage
}
// 4
progressBar.isVisible = loadState is LoadState.Loading
tvErrorMessage.isVisible = loadState !is LoadState.Loading
btnRetry.isVisible = loadState !is LoadState.Loading
}
There are a couple of things to explain here:
-
- You wire up the view as local properties:
- tvErrorMessage will display an error message.
- progressBar will display the loading state.
- btnRetry will retry the network call if it fails.
- You set the click listener for
btnRetryto invoke the retry action in thePagingSource - You create a function named
bindStatethat takes inLoadStateas an argument.
LoadStateis a sealed class that can have any of the following states:- NotLoading: No loading of data happening, and no error.
- Loading: Data is loading.
- Error: Fetching data ends with an error.
-
The value of
LoadStateis used to toggle visibility of views.
Next, replace the TODO() inside onBindViewHolder() with the following code:
holder.bindState(loadState)
Here you’re calling bindState() with the state that onBindViewHolder() method provides.
Next, you’ll wire up the RedditLoadingAdapter to your RecyclerView so as to start handling the loading state.
Navigate to ui/RedditPostsActivity.kt. Append the following code inside the setupViews():
rvPosts.adapter = redditAdapter.withLoadStateHeaderAndFooter(
header = RedditLoadingAdapter { redditAdapter.retry() },
footer = RedditLoadingAdapter { redditAdapter.retry() }
)
Here, you add another adapter to your RecyclerView. You use withLoadStateHeaderAndFooter, which takes two parameters: header and footer. For both, you use the RedditLoadingAdapter you created earlier. Notice how redditAdapter.retry() is used to retry network calls.
Build and run, and you’ll see the ProgressBar when loading new pages.
To display the error text in the footer, set your phone to airplane mode and try scrolling to the end of the list. Now you’ll see an error message:



