Better Programming

Advice for programmers.

Follow publication

How to Use the Android Paging3 Library With Jetpack Compose

Daniel Dimovski
Better Programming
Published in
6 min readSep 14, 2022
Google Books API app with Jetpack Compose
Image by author

This article will focus on explaining how to implement pagination in Android, following the latest trends in Android development. We will utilise the latest version of paging in Android — Paging3 and will explore how to integrate this with Jetpack Compose.

Spoiler alert: it’s simpler than you think!

To page or not to page — that is the question

When should you implement pagination? If it is a relatively small set of data that needs to be fetched and you know you need to present all of this data on the UI, there is no need to page the response.

Paging makes sense when you are loading large amounts of data, that you might, or might not need, depending on user interaction. Or, when the amount of data is not predictable — you don’t know how big the response will be before you actually make the call. In cases like that it’s smart to use pagination, to gradually fetch the data and present it to the user as they scroll through the page. This is known as infinite scrolling, and in this article, we’ll take a look at how to implement it using Google’s latest paging library and Jetpack Compose.

1. The Basics

First of all, we will need an API that supports paging. And for that purpose, we’ll use Google’s Book API. Pretty convenient, I know.

We will be making a one-screen application that consumes this API and allows the user to search for a book. And we’ll utilise paging for compose, to make the loading of the books faster and more efficient. We’ll only load 10 books per request, and as the user scrolls, we’ll load more. That way we only fetch as many books as we need, making the network requests faster and the user experience better.

We will use the latest versions of the dependencies to this date:

implementation "androidx.paging:paging-runtime:3.1.1"
implementation "androidx.paging:paging-compose:1.0.0-alpha16"

Please make sure you always use compatible versions of the standard paging library and paging for compose. Incompatible versions may lead to crashes.

Note: For the sake of simplicity, I will omit using Koin or other dependency injection frameworks in this example.

2. The Data Layer

The data layer is the same as any other — consisting of a repository and an API-consuming service, implemented with Retrofit in this case:

The only key difference is the Paging Source, which is a specific part of the Paging library. This is where the magic happens:

The query parameter is there only for the search functionality and is not directly related to the paging.

As you can see, this class needs to inherit from the PagingSource abstract class and implement two methods. Let’s implement them:

In the load() method, we make the call to the API. Of course, we need the limit and startIndex parameters here. These are provided for us by the Paging library. The limit parameter indicates how many items should be fetched in one call. It is defined by the loadSize property of the LoadParams.

On the other hand, the startIndex parameter represents the index of the first element from where we want to start the fetch (this might vary between APIs, some use page numbers instead). It is provided by the key property of the LoadParams.

We make the call to the endpoint with these two properties and wrap the result in a Page instance. Here we also need to provide the previous and the next key. This is essential for fetching the data properly. The logic here is that the prevKey should represent the previous page and the nextKey should indicate which page should be loaded next (it will be passed to this method the next time it is called as key).

In case we reach the final page, we pass null, to indicate that the whole data has been loaded.

override fun getRefreshKey(state: PagingState<Int, Book>): Int =
((state.anchorPosition ?: 0) - state.config.initialLoadSize / 2)
.coerceAtLeast(0)

The getRefreshKey() method provides information to the library on which page to load in case the data is invalidated.

Implementing this method improperly can lead to reloading the data whenever a refresh is performed, rather than updating the current list and keeping the position in the list.

3. The ViewModel

The ViewModel represents a link between the View and the Data layer. It’s pretty simple when it comes to paging through. You only need a few lines to connect everything together.

Simple as that. We create a Pager with a PagingConfig and provide the pager with a PagingSource. Then we wrap all this into a flow to make it easy to consume by the UI. That’s it!

4. The View

I will assume that you have a basic knowledge of Compose and that you know how to create a composable screen and render it in an activity. If you don’t, you can find the full code at the end of this article and see how exactly everything is wired together.

Remember I said that wrapping the Pager in a Flow will be useful for later? Here’s how convenient it’s to consume the flow in the UI — one line:

mainViewModel.bookPager.collectAsLazyPagingItems()

What does this line do? Basically, exactly what its name says — it collects the flow’s data as lazy items. And that’s exactly what we need. Compose knows how to handle this object and to request more items when the user reaches the end of the list. So, we pass this object to the composable screen.

The LazyPagingItems can be passed to one of the lazy layouts provided by Compose: LazyColumn, LazyRow, LazyHorizontalGrid or LazyVerticalGrid. More on that can be found here.

One more important thing to note here is handling the loading states. The LazyPagingItems contains information the LoadState. In this example, we are only reacting to the refresh event (initial load or invalidating the data source), but you can also handle the append, prepend and a couple more, and show the appropriate UI to the user.

5. Analysis

Please note that the excerpts in this article are exactly that — excerpts and are (probably) not compilable because I intentionally left out parts of the code to make it legible. You can find a link to the full code near the end of the article.

Now, let’s see how everything works together.

I’ve added numbers to the items & attached the Network Inspector to the application to show you how paging works in the background.

How does paging work behind the scenes?

As you can see in the code, we’ve set the load size to be 10. But in the initial load, the maxResults parameter is 30. Why?

The paging library uses preloading — it loads the requested load size * 3, by default. This is so that the user has enough content to look at when the data is initially loaded. All subsequent loads are indeed fetching 10 items per request. You can alter this behaviour by defining the initialLoadSize property of the PagingConfig.

So, initially, we’ve loaded 30 items. But when we get to the 20th item, a new request is made. This is another feature of the Paging library known as prefetchDistance. It defaults to the pageSize you’ve defined in the PagingConfig, but if you want, you can override it when defining the PagingConfig.

6. Epilogue

Paging3 offers convenient methods to load items lazily and present it to the user without noticeable delays in the UI. And while you can use it with the old XML-defined views, the integration with Compose is really seamless. Use paging whenever you want to load big sets of data instead of fetching it all at once.

The full code can be found on GitHub:

I’ve published a follow-up article that extends on this codebase. If you care curious about side-effects in Jetpack Compose, take a look at Splash Screen with Jetpack Compose: Side-Effects in Compose & How to Use Them

Sign up to discover human stories that deepen your understanding of the world.

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

Daniel Dimovski
Daniel Dimovski

Written by Daniel Dimovski

Software Engineer / Android Developer

Responses (2)

Write a response