You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.09.03 06:39:27