Paging Library in Jetpack Compose with Unit Tests

Özgür Taş
5 min readFeb 19, 2023

--

We’re going to implement and use Paging Compose library step by step in this article.

Pagination In Compose

First of all, let’s add paging compose library into the app/build.gradle file.

implementation 'androidx.paging:paging-compose:1.0.0-alpha17'

As you can see it is still in alpha(non-stable) version.

Let’s check overview of the pagination in Andoid development.

With recommended app architecture, your pagingSource or RemoteMediator belongs to your Data Layer. We create the Pager class in the ViewModel and observe the data in the UI Layer.

I have used Github’s Users Api which supports pagination. For pagination, you pass 2 parameters to the UserApi which are starting position (Int) and per page (Int). I decided to show 20 users per page and start with 0 position. Every time user paginates I will get plus 20 users.

UserApi

Since our api supports pages, we can use PagingSource implementation. Check out for more information about it.

UsersPagingSource

In the UsersPagingSource, when api supports next pages, you use “nextKey” parameter. Unless you pass “null” to this parameter the pagination will continue. So, in our case, if there is no more user data we stop the pagination. This applies to the previous pages. In this case, you will pass the data to the “prevKey” parameter. And, of course, if the api supports both next and previous pages you can apply same conditions to both key parameters. In this case, our load function will be like this:

In the UsersViewModel, we create Pager class and pass the paging source that we created. After that, you start observing the data in your compose screen(it can be Fragment or Activity) AKA UI Layer.

In Compose Screen, to be able to observe paging data, we use .collectAsLazyPagingItems() api and use it in the LazyColumn to display our users. This api belongs to Compose Paging library.

val users = viewModel.users.collectAsLazyPagingItems()

Let’s talk about pagination states. There are 3 states:

  • Refresh (Initial State)
  • Append (Next page pagination state)
  • Prepend (Previous page pagination state)

And each state has 3 inner states:

  • Loading
  • Error
  • NotLoading

For our app we will have 6 possible cases to show user:

  • Loading (Initial) Case
  • Empty (Initial) Case
  • Error (Initial) Case
  • Loading (Append) Case
  • Error (Append) Case
  • Success (Initial or Append) case

For handling initial cases we can use this code block:

Initial States

When it is initial api call I want to show a bigger loading item. That’s why the size is 64.dp.

Initial Loading Item

When any error happens in the initial api call, we show a try again button. When the user clicks on this button, we call users.refresh() function to trigger getUsers api from scratch.

Initial Error Item

And, for the empty case, we check data size in the NotLoading case.

Initial Empty Item

Next is handling pagination states and its UI components.

Pagination States

Every time when pagination is requested, loading item will be displayed.

Pagination Loading Item

If any error happens while paginating, we show a refresh button to give ability to user to refresh pagination. When user clicks refresh button, we call users.retry() function to trigger getUsers api with last pagination parameters, not from scratch.

Pagination Error Item

And for success data case, we use items() function of LazyColumn which we don’t have to deal with RecyclerView anymore and its lovely Adapter and lovely ViewHolders. :)

Initial or Pagination User Item

You can check all these custom Compose UIs and Compose best practices such as Custom Preview Annotations in the sample project.

Custom Compose UIs

For the last part we will talk about how to test our pagination. I’ve written 3 test cases.

  • Success LoadResult Page
  • Error LoadResult Page
  • Success LoadResult Page For Pagination

Let’s check the last case:

As a given I will create paginated params which is the first page is loaded:

val params = PagingSource
.LoadParams
.Append(
key = 20,
loadSize = 1,
placeholdersEnabled = false
)

Then create the expected params with next key equals 40 since I paginate every time 20 users and the actual data coming from api:

val expected = PagingSource
.LoadResult
.Page(
data = userDtos,
prevKey = null,
nextKey = 40
)

I demonstrate pagination to pass given params to the load() function of the PagingSource.

val actual = usersPagingSource.load(params = params)

Finally, check results are equal:

assertEquals(expected, actual)

Full test case will be like this:

Success LoadResult Page For Pagination Unit Test Case

All other unit tests are here.

I know it may seem much, but once you try it, you will see how easy the paging library is. You can check and download the sample project here.
See you in the following article. 👋💻

--

--

Özgür Taş
Özgür Taş

No responses yet