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

强制重新赋值LiveData自身触发观测,这种写法是否属于不良代码?

嵌套RecyclerView效果图

问题背景

我当前使用嵌套RecyclerView实现业务功能:

  • 图中红色框为Routine Item(父项),蓝色框为Routine Item内的Detail Item(子项)
  • 点击ADD ROUTINE按钮可动态添加父项,点击对应父项的ADD按钮可动态添加子项

当前功能本身运行完全正常,但我对自己的代码实现存在疑问:
我使用ViewModel观测并更新父项的增删操作,但LiveData无法感知父项内部子项的变更,我判断原因是LiveData仅能检测列表本身的增删操作。
因此我添加了_items.value = _items.value代码,实现子项增删时也能触发LiveData的观测回调,这种写法下我甚至不需要在子Adapter中调用notifyDataSetChanged()这类更新代码。
功能虽然成功实现,但我不确定这种写法是否规范正确。
如果需要查看更多相关代码可以告知我!


Fragment.kt 代码

class WriteRoutineFragment : Fragment() {
    private var _binding : FragmentWriteRoutineBinding? = null
    private val binding get() = _binding!!
    private lateinit var adapter : RoutineAdapter
    private val vm : WriteRoutineViewModel by viewModels { WriteRoutineViewModelFactory() }

    override fun onCreateView(inflater: LayoutInflater,
                              container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        _binding = FragmentWriteRoutineBinding.inflate(inflater, container, false)

        adapter = RoutineAdapter(::addDetail, ::deleteDetail)
        binding.rv.adapter = this.adapter
        return binding.root
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        
        getTabPageResult()

        // RecyclerView Update
        vm.items.observe(viewLifecycleOwner) { updatedItems ->
            adapter.setItems(updatedItems)
        }
    }

    private fun getTabPageResult() {
        val navController = findNavController()
        navController.currentBackStackEntry?.also { stack ->
            stack.savedStateHandle.getLiveData<String>("workout")?.observe(
                viewLifecycleOwner, Observer { result ->
                    vm.addRoutine(result) // ADD ROUTINE
                    stack.savedStateHandle?.remove<String>("workout")
                }
            )
        }
    }

    private fun addDetail(pos: Int) {
        vm.addDetail(pos)
    }

    private fun deleteDetail(pos: Int) {
        vm.deleteDetail(pos)
    }
}

ViewModel 代码

class WriteRoutineViewModel : ViewModel() {
    private var _items: MutableLiveData<ArrayList<RoutineModel>> = MutableLiveData(arrayListOf())

    val items: LiveData<ArrayList<RoutineModel>> = _items

    fun addRoutine(workout: String) {
        val item = RoutineModel(workout, "TEST")
        _items.value?.add(item)
//        _items.value = _items.value
    }

    fun addDetail(pos: Int) {
        val detail = RoutineDetailModel("TEST", "TEST")
        _items.value?.get(pos)?.addSubItem(detail) // 父项内部的详情变更无法被LiveData感知
        _items.value = _items.value // 这种写法正确吗?
    }

    fun deleteDetail(pos: Int) {
        if(_items.value?.get(pos)?.getSubItemSize()!! > 1)
            _items.value?.get(pos)?.deleteSubItem() // 这种写法正确吗?
        else
            _items.value?.removeAt(pos)
        _items.value = _items.value // 这种写法正确吗?
    }
}

解答

关于_items.value = _items.value写法的合理性

这种写法功能上可行,但不属于最优实践:

  1. 逻辑上是通顺的:LiveData触发观察者回调的核心是调用setValue/postValue方法,和新旧值是否为同一个引用无关,你主动把现有列表重新赋值给LiveData的value,本质是主动触发了setValue操作,自然能让观察者收到更新回调,实现效果符合预期。
  2. 存在明显缺陷:这种写法会触发父RecyclerView的全量刷新,所有父项都会重新绑定、内部的子RecyclerView也会全部重绘,在列表项数量较多的场景下性能损耗很大,同时会丢失默认的Item增删动画。

更规范的优化方案

可以根据业务复杂度选择两种优化方向:

  1. 局部刷新方案(性能最优):操作子项时不需要触发整个父列表的LiveData更新,直接通过回调通知父Adapter,拿到对应位置的子Adapter后调用notifyItemInserted/notifyItemRemoved这类局部刷新方法即可,仅会刷新被改动的子项,性能损耗最小。
  2. 单向数据流方案(易维护):如果希望统一用LiveData驱动所有UI更新,可以将列表改为不可变类型,每次修改(不管是增删父项还是修改内部子项)都生成一个新的列表对象赋值给LiveData,同时给父Adapter配置DiffUtil实现自动局部刷新,既保留了数据单向更新的清晰逻辑,又避免了全量刷新的性能问题。如果子项逻辑比较复杂,也可以给每个父项的子列表单独声明LiveData,单独观测子列表变更来驱动子RecyclerView刷新,层级更清晰。

内容的提问来源于stack exchange,提问作者ybybyb

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.05 22:00:01