2

beta01Paging 3 版本开始,在从 a 刷新 PagingData 时,有时会出现刷新完成后仍然执行上一代RemoteMediator的旧请求。APPEND这似乎是从这个 commit读取的预期行为。发生这种情况时,旧APPEND请求会调用RemoteMediator'sload方法,但使用过时的PagingState. PagingState如果我们在加载函数中使用其中的信息(例如,下面的代码片段用于在数据库lastItemOrNull中查找项目),那么这个过时的可能会导致错误和崩溃。RemoteKeys这个突破性的变化(这也打破了相应的代码实验室) 根本没有在发行说明中提及。我们应该如何处理这件事?

这是一个与beta01. 该getRemoteKeyForLastItem方法可以返回null(因为 oldPagingState正在寻找在上一个中删除的数据库条目REFRESH)导致InvalidObjectException抛出 。

private const val GITHUB_STARTING_PAGE_INDEX = 1

@OptIn(ExperimentalPagingApi::class)
class GithubRemoteMediator(
        private val query: String,
        private val service: GithubService,
        private val repoDatabase: RepoDatabase
) : RemoteMediator<Int, Repo>() {

    override suspend fun load(loadType: LoadType, state: PagingState<Int, Repo>): MediatorResult {

        val page = when (loadType) {
            LoadType.REFRESH -> GITHUB_STARTING_PAGE_INDEX
            LoadType.PREPEND -> return MediatorResult.Success(true)
            LoadType.APPEND -> {
                // this can run with an outdated PagingState from the previous RemoteMediator instance, causing the Exception to be thrown
                val remoteKeys = getRemoteKeyForLastItem(state)
                if (remoteKeys == null || remoteKeys.nextKey == null) {
                    throw InvalidObjectException("Remote key should not be null for $loadType")
                }
                remoteKeys.nextKey
            }

        }

        val apiQuery = query + IN_QUALIFIER

        try {
            val apiResponse = service.searchRepos(apiQuery, page, state.config.pageSize)

            val repos = apiResponse.items
            val endOfPaginationReached = repos.isEmpty()
            repoDatabase.withTransaction {
                if (loadType == LoadType.REFRESH) {
                    repoDatabase.remoteKeysDao().clearRemoteKeys()
                    repoDatabase.reposDao().clearRepos()
                }
                val prevKey = if (page == GITHUB_STARTING_PAGE_INDEX) null else page - 1
                val nextKey = if (endOfPaginationReached) null else page + 1
                val keys = repos.map {
                    RemoteKeys(repoId = it.id, prevKey = prevKey, nextKey = nextKey)
                }
                repoDatabase.remoteKeysDao().insertAll(keys)
                repoDatabase.reposDao().insertAll(repos)
            }
            return MediatorResult.Success(endOfPaginationReached = endOfPaginationReached)
        } catch (exception: IOException) {
            return MediatorResult.Error(exception)
        } catch (exception: HttpException) {
            return MediatorResult.Error(exception)
        }
    }

    private suspend fun getRemoteKeyForLastItem(state: PagingState<Int, Repo>): RemoteKeys? {
        return state.pages.lastOrNull() { it.data.isNotEmpty() }?.data?.lastOrNull()
                ?.let { repo ->
                    repoDatabase.remoteKeysDao().remoteKeysRepoId(repo.id)
                }
    }
}
4

1 回答 1

1

我与 Dustin Lam 和 Yigit Boyar 进行了交谈,显然,处理此问题的最佳方法是在附加前添加不依赖于PagingState. 这意味着我们应该将远程键存储在与查询相关的表中,而不是在项目级别。

示例:

@Entity(tableName = "search_query_remote_keys")
data class SearchQueryRemoteKey(
    @PrimaryKey val searchQuery: String,
    val nextPageKey: Int
)
于 2021-02-13T10:24:26.197 回答