We’re going to implement and use Paging Compose library step by step in this article.
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.
Since our api supports pages, we can use PagingSource implementation. Check out for more information about it.
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:
When it is initial api call I want to show a bigger loading item. That’s why the size is 64.dp.
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.
And, for the empty case, we check data size in the NotLoading case.
Next is handling pagination states and its UI components.
Every time when pagination is requested, loading item will be displayed.
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.
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. :)
You can check all these custom Compose UIs and Compose best practices such as Custom Preview Annotations in the sample project.
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:
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. 👋💻