Paging Library helps you fetch loads of data from Network or from local source effectively and efficiently.
Conventional way requires us to look for below parameters to do it effectively:
- Keeping track of “keys”
- Requesting correct next page
- And preventing duplicate network requests
- Tracking loading state i.e “load more”
Paging3 Android got you covered here :)
Library Architecture:
Repository Layer
PagingSource
loads data from any single source among network and local data source.RemoteMediator
handles paging from layered data source like combination of network datasource with local data cache.
ViewModel Layer
Pager
component provides public API for creatingPagingData
instance which is retrieved based onPagingSource
object andPagingConfig
object.Pager
supports flow for coroutines, flowable/observable for Rx and livedata out of the box.PagingConfig
allows you to set several parameters among which pageSize in mandatory and represents numbers of items to load from paging source at a time.
UI Layer
PagingDataAdapter is
aRecyclerView
adapter that handles paginated data and is a primary component in this layer.- The
PagingDataAdapter
listens to internalPagingData
loading events as pages are loaded and usesDiffUtil
on a background thread to compute fine-grained updates as updated content is received in the form of newPagingData
objects.
Let’s Start Coding:
Add below line to your module level build.gradle file under dependencies
implementation "androidx.paging:paging-runtime:3.0.1"
Creating a PagingSource requires below items:
- The type of the paging key — which is used retrieve next data like pageNum.
- The type of data loaded — Response entity that we source will supply
- Where is the data retrieved from i.e. Network Source or Local data source
class SamplePagingSource : PagingSource<Int, DataModel>() {
override fun getRefreshKey(state: PagingState<Int, DataModel>): Int? {
TODO("Not yet implemented")
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, DataModel> {
TODO("Not yet implemented")
}
}
The load()
function is called by the Paging library to asynchronously fetch more data as user scrolls and returns a LoadResult
which could be LoadResult.Page
if success or LoadResult.Error
in case of error. The LoadParams
object keeps information related to the load operation.
By default, the initial load size is 3 * page size. That way, Paging ensures that the first time the list is loaded the user will see enough items and doesn’t trigger too many network requests, if the user doesn’t scroll past what’s loaded.
Below is sample code for repository:
class SampleRepository(private val service: SampleService) {
fun getDataStream(query: String): Flow<PagingData<DataModel>> {
return Pager(
config = PagingConfig(
pageSize = NETWORK_PAGE_SIZE,
enablePlaceholders = false
),
pagingSourceFactory = { SamplePagingSource(service, query) }
).flow
}
companion object {
const val NETWORK_PAGE_SIZE = 50
}
}
Refer below for ViewModel code:
class SampleViewModel(
...
) : ViewModel() {
override fun onCleared() {
...
}
private fun fetchData(): Flow<PagingData<DataModel>> =
repository.getDataStream().cachedIn(viewModelScope)}
Flow<PagingData>
has a handy cachedIn()
method that allows us to cache the content of a Flow<PagingData>
in a CoroutineScope
. Since we're in a ViewModel
, we will use the androidx.lifecycle.viewModelScope
.
Note: If you’re doing any operations on the
Flow
, likemap
orfilter
, make sure you callcachedIn
after you execute these operations to ensure you don't need to trigger them again.
Make your adapter understand PagingData by extending your list adapter by PagingDataAdapter
which is very similar to RecyclerView.Adapter as shown in below code.
class SampleAdapter : PagingDataAdapter<Key, SampleViewHolder>(DATA_COMPARATOR) {
// TODO : Add Body
}
Paging 3.0 does lot for us under the table
- Handles in-memory cache.
- Requesting data when users is about to reach end of list.
References
https://developer.android.com/topic/libraries/architecture/paging/v3-overview