Handling Loading states of Android Paging 3

Abu Yousuf
4 min readOct 10, 2023

--

A guide to manage and present load states of paging library

Photo by Mike van den Bos on Unsplash

Previously I wrote on Paging 3 on Android and Testing Android PagingSource. If you have missed those, please check it out first.

In this article, I will write about the loading states of the paging library.

Paging library tracks data request states. LoadState class represents the states of a request.

Loading states

LoadState objects can have three forms:

There are two ways you can use LoadState to show the state in UI.

  1. Registering a listener with the PagingDataAdapter . In this approach, you have to present a state outside of the RecyclerView
  2. Using LoadStateAdapter to present the state directly in the RecyclerView

Before using LoadState you need to know about LoadType and CombinedLoadStates.

LoadType is an Enum and it has 3 type

CombinedLoadStates is a collection of pagination LoadStates for both a PagingSource, and RemoteMediator. It contains LoadState for LoadType refresh, prepend, append, and data source type (PagingSource , RemoteMediator).

For better understanding check the official doc

Let’s check how can we show the loading state.

Loading state with a listener

To get the loading state for general use in your UI, you can use the loadStateFlow stream or the addLoadStateListener() method provided by your PagingDataAdapter. loadStateFlow.collectLatest will give you CombinedLoadStates so you can easily update your UI

Below is the code for updating UI with a listener

lifecycleScope.launch {
newsAdapter.loadStateFlow.collectLatest {
binding.progressLayout.isVisible = (it.refresh is LoadState.Loading) || (it.append is LoadState.Loading)
binding.layoutError.isVisible = (it.refresh is LoadState.Error) || (it.append is LoadState.Error)
}
}

Here to show progress I checked both refresh and append LoadState is Loading because progress may need to show(actually depends on your requirement) for both refresh, append load type.

Similarly, to show an error, I checked LoadState is Error

So it’s easy and simple

Loading state with LoadStateAdapter

The purpose of LoadStateAdapter is to present the loading state directly in RecyclerView. So how does this work? Let's see.

LoadStateAdapter provides access to the current load state of paged data, which you can pass to a custom view holder that displays the information.

First, create a view holder that is responsible for showing loading and error views in UI.

Second, create a class that implements LoadStateAdapter. In onBindViewHolder() you will get LoadState which you can pass to the method of view holder that will display information in UI.

Let’s see the implementation

class LoadStateViewHolder(
private val binding: LayoutLoadStateBinding,
val retry: () -> Unit
) : RecyclerView.ViewHolder(binding.root) {

fun bind(loadState : LoadState) {
binding.apply {
layoutError.isVisible = loadState is LoadState.Error
progressBar.isVisible = loadState is LoadState.Loading
}
}
}

Here parameter retry can be used to retry failed load requests. bind will update UI.

Display the loading state as a header or footer

To display the loading progress in a header and a footer, you can use withLoadStateHeaderAndFooter() method from your PagingDataAdapter object

val headerStateAdapter = NewsLoadStateAdapter(newsAdapter::retry)
val footerStateAdapter = NewsLoadStateAdapter(newsAdapter::retry)

newsAdapter.withLoadStateHeaderAndFooter(
header = headerStateAdapter,
footer = footerStateAdapter
)

If you need to display the loading state with only the header then use withLoadStateHeader() and only the footer use withLoadStateFooter(). Now you can see loading states as header and footer. If you want to show progress or error for the initial data load then this approach will not work. Let's find out why it will not work for the initial data load.

Let’s check the implementation. Below is the implementation of the method withLoadStateHeaderAndFooter()

fun withLoadStateHeaderAndFooter(
header: LoadStateAdapter<*>,
footer: LoadStateAdapter<*>
): ConcatAdapter {
addLoadStateListener { loadStates ->
header.loadState = loadStates.prepend
footer.loadState = loadStates.append
}
return ConcatAdapter(header, this, footer)
}

Here a load state listener is added, prepend LoadState is assigned to the header LoadState and append LoadState is assigned to the footer LoadState. So the header will work for only prepend LoadState and the footer for only append LoadState . But to show initial progress we need refresh LoadState . Hope now you understand why it will not work for the initial loading state.

Display the initial loading state

I am sure now you know the solution of showing the initial load state in UI.

The solution is to use refresh LoadState instead of prepend LoadState .
Check the code below.

newsAdapter.addLoadStateListener { loadStates ->
headerStateAdapter.loadState = loadStates.refresh
footerStateAdapter.loadState = loadStates.append
}
val concatAdapter = ConcatAdapter(headerStateAdapter, newsAdapter, footerStateAdapter)
binding.recyclerView.adapter = concatAdapter

Thanks for reading. I hope if you are new to the Android paging library this article will help you to handle loading states.

Happy coding. :)

--

--