JetPack Paging 3.0 Android

YASH AGARWAL
4 min readJun 20, 2020

--

Paging is a live way to load small chunks of data incrementally from the local storage or over the network and display it gracefully within your app’s UI.

As a part of the Android 11 beta release, Google has also updated its Jetpack series which also includes Paging library 3.0. A completely rewritten library using Kotlin’s coroutines along with separators. The new Paging 3.0 is designed to fit into the recommended Android App Architecture and provide first-class Kotlin support.

So what’s new in JetPack Paging 3.0

  • Built-in support for error handling, including refresh and retry mechanism.
  • First-class support for Kotlin’s coroutines and Flow, as well as LiveData and RxJava.
  • Built-in separator, header, and footer support.
  • Automatically requests the correct page when the user has scrolled to the end of the list.
  • Ensures that multiple requests aren’t triggered at the same time.

Library architecture

  • Repository layer:- A repository that works with the database and the web API service, providing a unified data interface.
  • ViewModel layer:- A component that connects the repository layer to the UI.
  • UI layer:- A visual representation of the data to the user.
Paging 3.0 architecture

Here is how the set up and code looks:-

Dependency Setup

def paging_version = "3.0.0-alpha01"//Paging3.0 core dependencies Injection
implementation "androidx.paging:paging-runtime:$paging_version"

Code Setup

This is the Heart of the Paging where we connect to our Back-end. We extend this Paging Source class which has a singleLoadmethod that we need to implement. Here we call our API service and return the result to the Page.

class PagingDataSource(Page
private val apiService: APIService
) : PagingSource<Int, Model>() {
private val initialPageIndex: Int = 1
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Model> {
val position = params.key ?: initialPageIndex

return try {
val response = apiService.requestAPI(position,params.loadSize)
val resultantItems = response.items
LoadResult.Page(
data = resultantItems,
prevKey = if (position == initialPageIndex) null else position - 1,
nextKey = if (resultantItems.isEmpty()) null else position + 1
)
} catch (exception: IOException) {
return LoadResult.Error(exception)
} catch (exception: HttpException) {
return LoadResult.Error(exception)
}
}
}

Once you have done with the PagingSource now you can create the Pager in ViewModel which provides the Configuration of Page Size which help to get the flow out of it.

// ViewModel support for Kotlin’s coroutinesval apiData = Pager(PagingConfig(pageSize = 20),pagingSourceFactory = { PagingDataSource(service) }).flowor// ViewModel support for Flowfun apiData(): Flow<PagingData<Repo>> {
return Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { PagingDataSource(service) }
).flow
}

Request and cache PagingData in the ViewModel

With Paging 3.0 we don’t need to convert our Flow to LiveData anymore. To survives the screen configuration changes from Portrait to Landscape and ViceVersa you can cache the apiData result to be store in an in-memory cache.

// ViewModel support for Kotlin’s coroutinesval apiData = Pager(PagingConfig(pageSize = 20),pagingSourceFactory = { PagingDataSource(service) }).flow.cachedIn(viewModelScope)or// ViewModel support for Flowfun apiData(): Flow<PagingData<Repo>> {
return Pager(
config = PagingConfig(pageSize = 20),
pagingSourceFactory = { PagingDataSource(service) }
).flow.cachedIn(viewModelScope)
}

Built-in Handy cachedIn() method allows us to cache the content of a PagingData in ViewModel and Since we’re in a ViewModel, we will use the androidx.lifecycle.viewModelScope.

Presenting data to the UI Layer

Finally, in our UI layer, we collect all the data from PagingData and then pass the data into our PagingDataAdapter then will take care to display the data.

// Activity CombinedLoadState
lifecycleScope.launch {
viewModel.apiData.collect {
it.let {
adapter.submitData(it)
}
}
}
// Adapter
class MyAdapter : PagingDataAdapter<Model, MyAdapter.MyViewHolder>(ModelDataComparator) {
// body is unchanged
}

Notifying loading state

In Order to notify the change in PagingData we can use CombinedLoadState callback for load state. CombinedLoadStateallows us to get the load state for the 3 different types of load operations:

  • CombinedLoadStates.refresh - represents the load state for loading the PagingData for the first time.
  • CombinedLoadStates.prepend - represents the load state for loading data at the start of the list.
  • CombinedLoadStates.append - represents the load state for loading data at the end of the list.
/*
* Progress Updater
* */
adapter.addLoadStateListener { loadState ->

if (loadState.refresh is LoadState.Loading ||
loadState.append is LoadState.Loading)
// Show ProgressBar
else {
// Hide ProgressBar

// If we have an error, show a toast
val errorState = when {
loadState.append is LoadState.Error -> loadState.append as LoadState.Error
loadState.prepend is LoadState.Error -> loadState.prepend as LoadState.Error
loadState.refresh is LoadState.Error -> loadState.refresh as LoadState.Error
else -> null
}
errorState?.let {
Toast.makeText(this, it.error.toString(), Toast.LENGTH_LONG).show()
}

}
}

Sample code

Do you want to see how Android JetPack Paging3.0 is used in existing apps? Go check its usage in the Application on Github

If you found this post useful, it would help others to find it if you could click on the 👏 icon below and also do follow for more articles like this — thanks!

Happy Coding :)

--

--