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

问题背景
我当前使用嵌套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写法的合理性
这种写法功能上可行,但不属于最优实践:
- 逻辑上是通顺的:LiveData触发观察者回调的核心是调用
setValue/postValue方法,和新旧值是否为同一个引用无关,你主动把现有列表重新赋值给LiveData的value,本质是主动触发了setValue操作,自然能让观察者收到更新回调,实现效果符合预期。 - 存在明显缺陷:这种写法会触发父RecyclerView的全量刷新,所有父项都会重新绑定、内部的子RecyclerView也会全部重绘,在列表项数量较多的场景下性能损耗很大,同时会丢失默认的Item增删动画。
更规范的优化方案
可以根据业务复杂度选择两种优化方向:
- 局部刷新方案(性能最优):操作子项时不需要触发整个父列表的LiveData更新,直接通过回调通知父Adapter,拿到对应位置的子Adapter后调用
notifyItemInserted/notifyItemRemoved这类局部刷新方法即可,仅会刷新被改动的子项,性能损耗最小。 - 单向数据流方案(易维护):如果希望统一用LiveData驱动所有UI更新,可以将列表改为不可变类型,每次修改(不管是增删父项还是修改内部子项)都生成一个新的列表对象赋值给LiveData,同时给父Adapter配置
DiffUtil实现自动局部刷新,既保留了数据单向更新的清晰逻辑,又避免了全量刷新的性能问题。如果子项逻辑比较复杂,也可以给每个父项的子列表单独声明LiveData,单独观测子列表变更来驱动子RecyclerView刷新,层级更清晰。
内容的提问来源于stack exchange,提问作者ybybyb
相关产品推荐
相关产品推荐

