MutableLiveData初始化后数据异常重复,如何控制?
MVVM架构下MutableLiveData数据重复问题
我正在通过搭建应用学习MVVM架构,用到了MutableLiveData、RecyclerView.Adapter和仓库模式。遇到的问题是:MutableLiveData初始化后数据出现重复,把它的值和尚未设置的值对比时,二者居然完全相同!怀疑它在后台某处自动设置了值,但不知道该怎么控制。
仓库层代码(初始化数据与MutableLiveData定义)
object ShopListRepositoryImpl: ShopListRepository { private val shopList = sortedSetOf(Comparator<ShopItem> {el1, el2 -> el1.id.compareTo(el2.id)}) private val shopListLiveData = MutableLiveData<List<ShopItem>>() init { // 初始化初始条目 for (i in 1..5) { shopList.add(ShopItem("product $i", i*i, i % 2 == 0)) } shopListLiveData.value = shopList.toList() } override fun addShopItem(shopItem: ShopItem) { shopList.add(shopItem) updateShopList() } override fun getShopList(): MutableLiveData<List<ShopItem>> { return shopListLiveData } ... private fun updateShopList() { shopListLiveData.value = shopList.toMutableList() } }
设计思路是仅使用一个MutableLiveData进行操作。
ViewModel相关代码
class MainViewModel: ViewModel() { private val repository = ShopListRepositoryImpl // GetShopListUseCase仅包含execute方法,返回仓库中的MutableLiveData private val getShopListUseCase = GetShopListUseCase(repository) ... val shopList = getShopListUseCase.execute() }
RecyclerView.Adapter数据更新逻辑
class MainActivityAdapter: RecyclerView.Adapter<MainActivityAdapter.MainActivityViewHolder>() { var shopItemsList = mutableListOf<ShopItem>() set(value) { val diffUtilCallback = MainActivityDiffUtilCallback(shopItemsList, value) DiffUtil.calculateDiff(diffUtilCallback).dispatchUpdatesTo(this) field = value } // onCreateViewHolder、onBindViewHolder、getItemViewType和getItemCount的重写实现 }
此Adapter通过DiffUtil完成所有操作,无需调用notifyDataSetChanged()。
Activity代码
class MainActivity : AppCompatActivity() { lateinit var binding: ActivityMainBinding lateinit var adapter: MainActivityAdapter lateinit var viewModel: MainViewModel override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) adapter = MainActivityAdapter() viewModel = ViewModelProvider(this)[MainViewModel::class.java] adapter.shopItemOnClickListener = { viewModel.changeEnabledState(it) } adapter.shopItemOnLongClickListener = { viewModel.delete(it) } setContentView(binding.root) setupRecyclerView() viewModel.shopList.observe(this) { adapter.shopItemsList = it.toMutableList() } } fun setupRecyclerView() { // 设置适配器、缓存池大小等 } }
更新内容:直接操作Adapter数据可正常工作
以下代码能正确更新RecyclerView数据,我把LiveData观察逻辑替换为点击浮动按钮添加新条目,这种方式没问题:
override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) binding = ActivityMainBinding.inflate(layoutInflater) adapter = MainActivityAdapter() viewModel = ViewModelProvider(this)[MainViewModel::class.java] adapter.shopItemOnClickListener = { viewModel.changeEnabledState(it) } adapter.shopItemOnLongClickListener = { viewModel.delete(it) } setContentView(binding.root) setupRecyclerView() binding.floatingActionButton.setOnClickListener { val newList = adapter.shopItemsList.toMutableList() newList.add(ShopItem("Shopitem${newList.size}", 0, true)) adapter.shopItemsList = newList } // viewModel.shopList.observe(this) { // adapter.shopItemsList = it.toMutableList() // } }
问题分析与解决方案
核心原因排查
- 仓库是单例
object,初始化阶段就给shopListLiveData赋值。当Activity首次观察该LiveData时,LiveData会立即推送当前持有的值;若后续发生配置变更(如屏幕旋转),Activity重建后再次观察,LiveData会再次推送数据,若DiffUtil逻辑有误,就会出现重复条目。 - 检查
ShopItem的equals/hashCode实现,以及MainActivityDiffUtilCallback的对比逻辑:如果areItemsTheSame或areContentsTheSame判断错误,DiffUtil会误将相同条目识别为新条目,导致重复添加。 - 若
ShopItem的id生成逻辑有问题,会导致sortedSet无法去重,仓库中直接出现重复数据。
- 仓库是单例
具体修复步骤
- 规范LiveData的可见性:仓库和ViewModel应返回
LiveData<List<ShopItem>>而非MutableLiveData,避免外部直接修改数据,保证单向数据流:// 仓库层修改 override fun getShopList(): LiveData<List<ShopItem>> { return shopListLiveData } - 使用viewLifecycleOwner观察:避免内存泄漏,同时确保生命周期匹配,防止重复触发观察回调:
viewModel.shopList.observe(viewLifecycleOwner) { adapter.shopItemsList = it.toMutableList() } - 修正DiffUtil回调逻辑:确保
areItemsTheSame对比唯一标识id,areContentsTheSame对比所有属性:class MainActivityDiffUtilCallback( private val oldList: List<ShopItem>, private val newList: List<ShopItem> ) : DiffUtil.Callback() { override fun getOldListSize() = oldList.size override fun getNewListSize() = newList.size override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { return oldList[oldItemPosition].id == newList[newItemPosition].id } override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean { val oldItem = oldList[oldItemPosition] val newItem = newList[newItemPosition] return oldItem.name == newItem.name && oldItem.count == newItem.count && oldItem.enabled == newItem.enabled } } - 检查ShopItem的id生成逻辑:确保每次新增的
ShopItem都有唯一id,防止仓库的sortedSet添加重复数据。
- 规范LiveData的可见性:仓库和ViewModel应返回
验证方式
- 在仓库的
updateShopList方法添加日志,查看shopList内容是否存在重复条目。 - 在LiveData的观察回调中添加日志,统计回调触发次数及接收的数据内容。
- 在仓库的
内容的提问来源于stack exchange,提问作者PythonNewbie
相关产品推荐
相关产品推荐

