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 无效。

如果您的数据库没有更新,您应该将 endOfPaginationReachedMediatorResult 设置为 true,因此您不希望失效。

顺便说一句,Room 在其 PagingSource 实现中自动处理此问题,因此您可能需要查看 Realm 是否提供了一些您 PagingSource 可以收听的回调,或者您可能需要自己跟踪.

编辑:缺少远程 APPEND 调用的根本原因最终没有将 nextKey 设置为 null