JetPack Paging 3.0 Android
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.
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 singleLoad
method 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
toLiveData
anymore. To survives the screen configuration changes from Portrait to Landscape and ViceVersa you can cache theapiData
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 aPagingData
in ViewModel and Since we’re in aViewModel
, we will use theandroidx.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. CombinedLoadState
allows us to get the load state for the 3 different types of load operations:
CombinedLoadStates.refresh
- represents the load state for loading thePagingData
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 :)