RecyclerView Adapter依赖两个Room数据源重复触发notifyDataSetChanged报错如何解决
解决方案
这里提供两种落地方式,优先推荐第一种从DAO层解决,实现成本最低也最高效:
方案1:Room 关联查询一次性返回带状态数据(最优)
直接通过SQL关联查询,让Room返回单个数据源,从根源避免多数据源同步问题:
- 先定义用于列表展示的POJO类:
data class PageWithControlState( @Embedded val page: PageEntity, val isControlledByCurrentUser: Boolean )
- 在DAO层写关联查询语句,传入当前登录用户ID即可:
@Query("SELECT p.*, CASE WHEN up.userId IS NOT NULL THEN 1 ELSE 0 END AS isControlledByCurrentUser FROM PageEntity p LEFT JOIN User_Page_Entity up ON p.pageId = up.pageId AND up.userId = :currentUserId") fun getAllPagesWithControlState(currentUserId: Long): LiveData<List<PageWithControlState>>
- 仅观察这一个LiveData即可,每次数据库变更只会触发一次回调,直接把返回数据交给Adapter渲染,不存在多次调用
notifyDataSetChanged的问题。
方案2:用MediatorLiveData合并双数据源(通用场景)
如果确实需要维护两个独立的数据源,可通过MediatorLiveData合并两个源的变更,只有两个源都返回过数据后才触发下游刷新:
// 定义合并后的LiveData val mergedPageLiveData = MediatorLiveData<List<PageWithControlState>>() // 缓存两个源的最新数据 private var pageList: List<PageEntity>? = null private var userControlList: List<User_Page_Entity>? = null // 添加两个数据源作为观察源 mergedPageLiveData.addSource(pageLiveData) { list -> pageList = list mergeDataIfReady() } mergedPageLiveData.addSource(userControlLiveData) { list -> userControlList = list mergeDataIfReady() } // 数据就绪后合并 private fun mergeDataIfReady() { val pages = pageList ?: return val controls = userControlList ?: return // 生成带状态的页面列表 val controlledPageIds = controls.map { it.pageId }.toSet() val result = pages.map { page -> PageWithControlState(page, controlledPageIds.contains(page.pageId)) } mergedPageLiveData.postValue(result) }
仅观察mergedPageLiveData即可,每次两个数据源任意更新,只会触发一次合并后的回调,避免多次刷新的冲突。
额外优化建议
替换全量刷新的notifyDataSetChanged为DiffUtil做差量更新,既可以提升列表性能,也能彻底避免ViewHolder重复detach的异常。
内容的提问来源于stack exchange,提问作者Omar Shawky
相关产品推荐
相关产品推荐

