Android Paging3搭配RemoteMediator时RecyclerView列表条目错乱如何解决?
问题修复方案
1. 修复Room查询Recipe的排序规则
列表错乱最核心的原因是从Room查询Recipe数据时没有指定排序字段,Room默认返回的记录顺序不固定,每次新增数据后查询结果顺序可能发生变化,导致PagingSource返回的列表顺序错乱,RecyclerView自然就会出现条目跳动、位置异常的问题。
请在你的RecipesDao查询所有Recipe的方法上添加排序规则,比如按Recipe的主键id升序,或者新增插入时间字段按插入时间排序:
@Query("SELECT * FROM recipes ORDER BY id ASC") fun getRecipes(): PagingSource<Int, Recipe>
2. 修复LoadStateListener的逻辑错误
你现在的LoadStateListener里连续两次给同一个View的isVisible赋值,后面的赋值会直接覆盖前面的判断,导致mediator的加载状态完全没有生效,会出现刷新时列表先显示、数据更新后又重新渲染的跳动问题。请将判断逻辑改为逻辑或:
recipesAdapter.addLoadStateListener { loadState -> binding.apply { val isRefreshLoading = loadState.mediator?.refresh is LoadState.Loading || loadState.source.refresh is LoadState.Loading recipesProgressBar.isVisible = isRefreshLoading val isRefreshSuccess = loadState.mediator?.refresh is LoadState.NotLoading && loadState.source.refresh is LoadState.NotLoading recipesRecyclerView.isVisible = isRefreshSuccess && recipesAdapter.itemCount > 0 val isRefreshError = loadState.mediator?.refresh is LoadState.Error || loadState.source.refresh is LoadState.Error buttonRecipesRetry.isVisible = isRefreshError textViewRecipesError.isVisible = isRefreshError textViewEmpty.isVisible = isRefreshSuccess && loadState.append.endOfPaginationReached && recipesAdapter.itemCount < 1 } }
3. 刷新完成后主动滚动到列表顶部
你没有处理刷新后的列表位置逻辑,刷新清空旧数据再插入新数据后,RecyclerView会默认停留在之前的滚动位置,就会出现条目出现在可见区域上方、不会回到顶部的问题。请在收集PagingData的逻辑里,提交新分页数据后主动滚动到顶部:
lifecycleScope.launch { viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) { viewModel.recipesFlow.collectLatest { pagingData -> recipesAdapter.submitData(pagingData) // 提交新的分页数据后滚动到顶部 binding.recipesRecyclerView.scrollToPosition(0) } } }
4. 校验Adapter的DiffUtil配置
请确认你的PagingDataAdapter的DiffUtil.ItemCallback配置正确,必须使用Recipe的唯一主键作为areItemsTheSame的判断依据,避免条目更新时判断错误导致跳动:
object RecipeDiffCallback : DiffUtil.ItemCallback<Recipe>() { override fun areItemsTheSame(oldItem: Recipe, newItem: Recipe): Boolean { // 用Recipe的唯一id判断是否是同一个条目 return oldItem.id == newItem.id } override fun areContentsTheSame(oldItem: Recipe, newItem: Recipe): Boolean { // 全量判断条目内容是否相等 return oldItem == newItem } }
5. 可选优化:修复RemoteKeys的冗余插入问题
你现在每次请求返回20条Recipe,就会生成20条主键相同的RemoteKeys插入数据库,因为设置了OnConflictStrategy.REPLACE,最终只会保留1条有效记录,前面19条都是无效插入,可以优化为只插入1条RemoteKeys即可:
appDatabase.withTransaction { if (loadType == LoadType.REFRESH) { appDatabase.remoteKeysDao().clearRemoteKeys() appDatabase.recipesDao().clearRecipes() } val nextKey = page + 20 // 只插入1条RemoteKeys即可,不需要循环20次 appDatabase.remoteKeysDao().insertAll(listOf(RemoteKeys(query = queryId, nextKey = nextKey))) appDatabase.recipesDao().insertAllRecipes(response.recipes) }
内容的提问来源于stack exchange,提问作者puszkinowski

