Android 中与 Objectbox 数据库的分页集成
Paging integration with Objectbox database in Android
我想将 Jetpack(Android 架构组件)中的 paging3 与 Objectbox 一起使用。但是在加载下一页时遇到麻烦。
当 recyclerview 向下滚动时,RemoteMediator 不会触发 LoadType.APPEND 事件。可能是什么原因?
依赖关系:
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'
分页源实现:
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 实现:
@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)
}
}
}
寻呼机创建过程:
@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
有一个 .invalidate()
函数,您可以调用它来触发 Paging 以创建新的 PagingData / PagingSource 对以反映 Realm DB 中的更改。
在您的 RemoteMediator
实现中,您应该从网络中获取项目,然后将它们写入数据库,然后在返回 MediatorResult.Success
之前使 PagingSource
无效。
如果您的数据库没有更新,您应该将 endOfPaginationReached
从 MediatorResult
设置为 true
,因此您不希望失效。
顺便说一句,Room 在其 PagingSource
实现中自动处理此问题,因此您可能需要查看 Realm 是否提供了一些您 PagingSource
可以收听的回调,或者您可能需要自己跟踪.
编辑:缺少远程 APPEND 调用的根本原因最终没有将 nextKey
设置为 null
。
我想将 Jetpack(Android 架构组件)中的 paging3 与 Objectbox 一起使用。但是在加载下一页时遇到麻烦。 当 recyclerview 向下滚动时,RemoteMediator 不会触发 LoadType.APPEND 事件。可能是什么原因?
依赖关系:
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'
分页源实现:
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 实现:
@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)
}
}
}
寻呼机创建过程:
@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
有一个 .invalidate()
函数,您可以调用它来触发 Paging 以创建新的 PagingData / PagingSource 对以反映 Realm DB 中的更改。
在您的 RemoteMediator
实现中,您应该从网络中获取项目,然后将它们写入数据库,然后在返回 MediatorResult.Success
之前使 PagingSource
无效。
如果您的数据库没有更新,您应该将 endOfPaginationReached
从 MediatorResult
设置为 true
,因此您不希望失效。
顺便说一句,Room 在其 PagingSource
实现中自动处理此问题,因此您可能需要查看 Realm 是否提供了一些您 PagingSource
可以收听的回调,或者您可能需要自己跟踪.
编辑:缺少远程 APPEND 调用的根本原因最终没有将 nextKey
设置为 null
。