Room查询数据正常但RecyclerView无法显示的问题排查

我正在开发与图示效果类似的界面,相关逻辑如下:
- 预置数据从
Room DB中读取,需要为每个标签页配置对应的数据 - 每个标签页对应一个
fragment,内部通过RecyclerView展示列表数据 - 不同标签页展示的数据内容不同,在
ViewModel中将当前选中标签对应的身体部位设置为LiveData并进行观察监听 - 预期效果:每次切换标签页时,从数据库读取当前标签对应的数据,设置到
RecyclerView中展示 - 当前故障:即使已经成功从数据库读取到数据,数据也无法设置到RecyclerView上,调试确认数据已正常返回,且排除适配器原因,需要排查遗漏的逻辑配置
相关代码定义
WorkoutList 实体类
@Entity data class WorkoutList( @PrimaryKey(autoGenerate = true) val id: Long = 0, val chest: List<String>, val back: List<String>, val leg: List<String>, val shoulder: List<String>, val biceps: List<String>, val triceps: List<String>, val abs: List<String> )
ViewModel 实现
class WorkoutListViewModel(application: Application) : AndroidViewModel(application){ private var _part :MutableLiveData<BodyPart> = MutableLiveData() private var result : List<String> = listOf() private val workoutDao = WorkoutListDatabase.getDatabase(application).workoutListDao() private val workoutListRepo = WorkoutListRepository(workoutDao) val part = _part fun setList(part : BodyPart) : List<String> { _part.value = part viewModelScope.launch(Dispatchers.IO){ result = workoutListRepo.getWorkoutList(part) } return result } }
Repository 实现
class WorkoutListRepository(private val workoutListDao: WorkoutListDao) { suspend fun getWorkoutList(part: BodyPart) : List<String> { val partList = workoutListDao.getWorkoutList() return when(part) { is BodyPart.Chest -> partList.chest is BodyPart.Back -> partList.back is BodyPart.Leg -> partList.leg is BodyPart.Shoulder -> partList.shoulder is BodyPart.Biceps -> partList.biceps is BodyPart.Triceps -> partList.triceps is BodyPart.Abs -> partList.abs } } }
Fragment 实现
class WorkoutListTabPageFragment : Fragment() { private var _binding : FragmentWorkoutListTabPageBinding? = null private val binding get() = _binding!! private lateinit var adapter: WorkoutListAdapter private lateinit var part: BodyPart private val viewModel: WorkoutListViewModel by viewModels() companion object { @JvmStatic fun newInstance(part: BodyPart) = WorkoutListTabPageFragment().apply { arguments = Bundle().apply { putParcelable("part", part) } } } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) arguments?.let { bundle -> part = bundle.getParcelable("part") ?: throw NullPointerException("No BodyPart Object") } } override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { _binding = FragmentWorkoutListTabPageBinding.inflate(inflater, container, false) binding.apply { adapter = WorkoutListAdapter() rv.adapter = adapter } val result = viewModel.setList(part) // 标签页切换时设置数据 viewModel.part.observe(viewLifecycleOwner) { _ -> // val result = viewModel.setList(part) adapter.addItems(result) } return binding.root } }
问题原因
核心问题出在协程异步执行与LiveData通知的时序错误,共有3个关键漏洞:
setList方法内的数据库查询运行在Dispatchers.IO协程上下文,属于异步操作,方法直接return的result永远是初始空列表,根本拿不到查询完成后的数据- 数据库查询完成后,只更新了ViewModel里的普通变量
result,没有通过LiveData的postValue/setValue通知观察者数据变更,观察者收到part变更通知时,查询操作大概率还没执行完,拿到的还是旧的空列表 - Fragment末尾重复写了一遍observe代码属于复制粘贴残留,会引发重复注册观察者的问题
修复步骤
第一步:改造ViewModel,将查询结果暴露为可观察的LiveData
把普通的result变量替换为LiveData,查询完成后直接更新数据,自动通知UI层:
class WorkoutListViewModel(application: Application) : AndroidViewModel(application){ private val _part = MutableLiveData<BodyPart>() // 将结果改为可观察的LiveData private val _workoutList = MutableLiveData<List<String>>() val workoutList: LiveData<List<String>> = _workoutList private val workoutDao = WorkoutListDatabase.getDatabase(application).workoutListDao() private val workoutListRepo = WorkoutListRepository(workoutDao) // 切换选中部位时自动触发查询 fun setPart(part: BodyPart) { _part.value = part // 在协程内查询,完成后直接更新列表LiveData viewModelScope.launch(Dispatchers.IO){ val list = workoutListRepo.getWorkoutList(part) _workoutList.postValue(list) } } }
第二步:修改Fragment的观察逻辑
删除重复的observe代码,改为观察列表数据的LiveData,不要拿setList返回的空值,补充Binding回收逻辑避免内存泄漏:
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { _binding = FragmentWorkoutListTabPageBinding.inflate(inflater, container, false) binding.apply { adapter = WorkoutListAdapter() rv.adapter = adapter } // 设置当前页面对应的部位,触发查询 viewModel.setPart(part) // 直接观察列表数据,数据返回后自动刷新适配器 viewModel.workoutList.observe(viewLifecycleOwner) { list -> adapter.addItems(list) } return binding.root } override fun onDestroyView() { super.onDestroyView() _binding = null }
额外适配点
如果标签页用ViewPager2实现,注意给ViewModel的作用域设置为宿主Activity,否则每个Fragment拿到的都是独立的ViewModel实例,切换标签的选中状态无法同步:
// 替换原来的viewModels()委托,共享宿主Activity的ViewModel private val viewModel: WorkoutListViewModel by activityViewModels()
内容的提问来源于stack exchange,提问作者ybybyb
相关产品推荐
相关产品推荐

