Image for post
Image for post

Paging3 — Doing Recyclerview Pagination the Right Way

Jetpack Paging library release version 3.0.0-alpha03 is full of new features and is Kotlin first with a pinch of Coroutines & Flow 😊.

Vikas Kumar
Sep 13 · 14 min read

Intro:

How it’s different:

Paging3 & Application Architecture:

Image for post
Image for post
Paging3 and App Architecture

So what’s the plan?

Before we start:

//paging3 
implementation "androidx.paging:paging-runtime:3.0.0-alpha03"
//optional dependency for RxJava support
implementation "androidx.paging:paging-rxjava2-ktx:3.0.0-alpha03"
//optional room db
implementation "androidx.room:room-runtime:2.3.0-alpha02"
implementation "androidx.room:room-ktx:2.3.0-alpha02"
kapt "androidx.room:room-compiler:2.3.0-alpha02"

Network as a Data Source 🌐:

interface DoggoApiService {

@GET("v1/images/search")
suspend fun getDoggoImages(@Query("page") page: Int, @Query("limit") size: Int): List<DoggoImageModel>

}
DoggoImagePagingSource load function
fun letDoggoImagesFlow(pagingConfig: PagingConfig = getDefaultPageConfig()): Flow<PagingData<DoggoImageModel>>{           //TODO
}
fun getDefaultPageConfig(): PagingConfig {
return PagingConfig(pageSize = DEFAULT_PAGE_SIZE, enablePlaceholders = false)
}
Image for post
Image for post
Remote Paging

PagingDataAdapter with loading states:

Image for post
Image for post
error handling in an adapter
Image for post
Image for post
Paging3 with load states

Room as a data source:

@Entity
data class DoggoImageModel(@PrimaryKey val id: String, val url: String)
@Dao
interface DoggoImageModelDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(doggoModel: List<DoggoImageModel>)

@Query("SELECT * FROM doggoimagemodel")
fun getAllDoggoModel(): PagingSource<Int, DoggoImageModel>

@Query("DELETE FROM doggoimagemodel")
suspend fun clearAllDoggos()

}
@Entity
data class RemoteKeys(@PrimaryKey val repoId: String, val prevKey: Int?, val nextKey: Int?)
@Dao
interface RemoteKeysDao {

@Insert(onConflict = OnConflictStrategy.REPLACE)
suspend fun insertAll(remoteKey: List<RemoteKeys>)

@Query("SELECT * FROM remotekeys WHERE repoId = :id")
suspend fun remoteKeysDoggoId(id: String): RemoteKeys?

@Query("DELETE FROM remotekeys")
suspend fun clearRemoteKeys()
}
@ExperimentalPagingApi
class DoggoMediator(doggoApiService: DoggoApiService, appDatabase: AppDatabase) :
RemoteMediator<Int, DoggoImageModel>() {

override suspend fun load(
loadType: LoadType, state: PagingState<Int, DoggoImageModel>
): MediatorResult {
//TODO
}
}
/**
* get the first remote key inserted which had the data
*/
private suspend fun getFirstRemoteKey(state: PagingState<Int, DoggoImageModel>): RemoteKeys? {
return state.pages
.firstOrNull() { it.data.isNotEmpty() }
?.data?.firstOrNull()
?.let { doggo -> appDatabase.getRepoDao().remoteKeysDoggoId(doggo.id) }
}
/**
* get the last remote key inserted which had the data
*/
private suspend fun getLastRemoteKey(state: PagingState<Int, DoggoImageModel>): RemoteKeys? {
return state.pages
.lastOrNull { it.data.isNotEmpty() }
?.data?.lastOrNull()
?.let { doggo -> appDatabase.getRepoDao().remoteKeysDoggoId(doggo.id) }
}
/**
* get the closest remote key inserted which had the data
*/
private suspend fun getClosestRemoteKey(state: PagingState<Int, DoggoImageModel>): RemoteKeys? {
return state.anchorPosition?.let { position ->
state.closestItemToPosition(position)?.id?.let { repoId ->
appDatabase
.getRepoDao().remoteKeysDoggoId(repoId)
}
}
}
/**
* this returns the page key or the final end of list success result
*/
suspend fun getKeyPageData(loadType: LoadType, state: PagingState<Int, DoggoImageModel>): Any? {
return when (loadType) {
LoadType.REFRESH -> {
val remoteKeys = getClosestRemoteKey(state)
remoteKeys?.nextKey?.minus(1) ?: DEFAULT_PAGE_INDEX
}
LoadType.APPEND -> {
val remoteKeys = getLastRemoteKey(state)
?: throw InvalidObjectException("Remote key should not be null for $loadType")
remoteKeys.nextKey
}
LoadType.PREPEND -> {
val remoteKeys = getFirstRemoteKey(state)
?: throw InvalidObjectException("Invalid state, key should not be null")
//end of list condition reached
remoteKeys.prevKey ?: return MediatorResult.Success(endOfPaginationReached = true)
remoteKeys.prevKey
}
}
}
fun letDoggoImagesFlowDb(pagingConfig: PagingConfig = getDefaultPageConfig()): Flow<PagingData<DoggoImageModel>> {
if (appDatabase == null) throw IllegalStateException("Database is not initialized")

val pagingSourceFactory = { appDatabase.getDoggoImageModelDao().getAllDoggoModel() }
return
Pager(
config = pagingConfig,
pagingSourceFactory = pagingSourceFactory,
remoteMediator = DoggoMediator(doggoApiService, appDatabase)
).flow
}
fun fetchDoggoImages(): Flow<PagingData<DoggoImageModel>> {
return repository.letDoggoImagesFlowDb().cachedIn(viewModelScope)
}
Image for post
Image for post
Paging with room

The Startup

Medium's largest active publication, followed by +730K people. Follow to join our community.

Vikas Kumar

Written by

Android Developer | Blogger | Design enthusiast | vikas.dev

The Startup

Medium's largest active publication, followed by +730K people. Follow to join our community.

Vikas Kumar

Written by

Android Developer | Blogger | Design enthusiast | vikas.dev

The Startup

Medium's largest active publication, followed by +730K people. Follow to join our community.

Medium is an open platform where 170 million readers come to find insightful and dynamic thinking. Here, expert and undiscovered voices alike dive into the heart of any topic and bring new ideas to the surface. Learn more

Follow the writers, publications, and topics that matter to you, and you’ll see them on your homepage and in your inbox. Explore

If you have a story to tell, knowledge to share, or a perspective to offer — welcome home. It’s easy and free to post your thinking on any topic. Write on Medium

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store