I want use paging3 from jetpack (Android Architecture Components) with Objectbox. But have troubles with loading next pages. When recyclerview scrolled down RemoteMediator doesnt trigger to LoadType.APPEND event. What could be the reasons?
Dependencies:
implementation "io.objectbox:objectbox-android:2.7.1"
implementation 'androidx.paging:paging-runtime:3.0.0-alpha06'
implementation 'androidx.recyclerview:recyclerview:1.2.0-alpha05'
implementation 'com.squareup.okhttp3:okhttp:4.8.1'
implementation 'com.squareup.okhttp3:logging-interceptor:4.8.1'
implementation 'com.squareup.retrofit2:retrofit:2.7.2'
implementation 'com.squareup.retrofit2:converter-gson:2.7.2'
Paging Source implementation:
class CustomPagingSource(
private val query: Query<Page>
) : PagingSource<Int, Showcase>() {
private var observer: DataObserver<List<Page>>? = null
private var subscription: DataSubscription? = null
init {
observer = DataObserver<List<Page>> { invalidate() }.also {
subscription = query.subscribe().onlyChanges().weak().observer(it)
}
}
override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Showcase> {
val currentPage = params.key ?: 1
val prevKey = if (currentPage == 1) null else currentPage - 1
val nextKey = currentPage + 1
val pages = when (params) {
is LoadParams.Refresh -> getPages(0, 1)
is LoadParams.Prepend -> null
is LoadParams.Append -> getPages(currentPage - 1, 1)
}
val items = pages?.map { it.items }?.flatten() ?: emptyList()
return LoadResult.Page(
data = items,
prevKey = prevKey,
nextKey = nextKey
)
}
override fun invalidate() {
super.invalidate()
subscription?.cancel()
subscription = null
observer = null
}
private fun getPages(startPosition: Int, count: Int): List<Page> =
this.query.find(startPosition.toLong(), count.toLong())
@OptIn(ExperimentalPagingApi::class)
override fun getRefreshKey(state: PagingState<Int, Showcase>): Int = 1
}
RemoteMediator implementation:
@OptIn(ExperimentalPagingApi::class)
class CustomRemoteMediator(
private val pullItems: suspend (page: Int, perPage: Int) -> List<Showcase>
) : RemoteMediator<Int, Showcase>() {
override suspend fun load(loadType: LoadType, state: PagingState<Int, Showcase>): MediatorResult {
val page = when (loadType) {
LoadType.REFRESH -> 1
LoadType.PREPEND -> return MediatorResult.Success(endOfPaginationReached = true)
LoadType.APPEND -> {
val nextKey = state.pages.lastOrNull()?.nextKey
nextKey ?: return MediatorResult.Success(endOfPaginationReached = true)
}
}
val perPage = if (loadType == LoadType.REFRESH) state.config.initialLoadSize else state.config.pageSize
return try {
val items = pullItems(page, perPage)
val endOfPagination = items.size < perPage
MediatorResult.Success(endOfPaginationReached = endOfPagination)
} catch (e: Exception) {
e.printStackTrace()
MediatorResult.Error(e)
}
}
}
Pager creating process:
@OptIn(ExperimentalCoroutinesApi::class)
private fun createPagingSource(): CustomPagingSource = CustomPagingSource(query)
@OptIn(ExperimentalCoroutinesApi::class, FlowPreview::class)
private val pager by lazy {
Pager(
config = PagingConfig(
pageSize = 5,
initialLoadSize = 5,
prefetchDistance = 1
),
remoteMediator = CustomRemoteMediator(::pullShowcases),
pagingSourceFactory = ::createPagingSource
).flow
}
/**
* pull items from API and put into database
*/
private suspend fun pullShowcases(page: Int, perPage: Int): List<Showcase> = withContext(Dispatchers.IO) {
val showcasesDTO = ApiService.retrofit.getMyShowcases(0.0, 0.0, page, perPage)
val showcases = showcasesDTO.map {
Showcase(
id = it.id,
title = it.title
)
}
showcaseBox.put(showcases)
val pageEntity = Page(page.toLong()).also {
pageBox.attach(it)
it.items.addAll(showcases)
}
pageBox.put(pageEntity)
return@withContext showcases
}
PagingSource
has an .invalidate()
function you can call to trigger Paging to create a new PagingData / PagingSource pair to reflect changes in your Realm DB.
In your RemoteMediator
implementation, you should fetch items from network, then write them to db and then invalidate the PagingSource
before returning MediatorResult.Success
.
You should set endOfPaginationReached
to true
from MediatorResult
, if there were no updates to your db and you therefore don't expect to invalidate.
Btw, Room automatically handles this in its PagingSource
implementation, so you may want to look into whether Realm offers some callbacks your PagingSource
can listen to or you may need to to keep track yourself.
EDIT: Root cause for missing remote APPEND call was not setting nextKey
to null
eventually.