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

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()
//    }
}

问题分析与解决方案

  1. 核心原因排查

    • 仓库是单例object,初始化阶段就给shopListLiveData赋值。当Activity首次观察该LiveData时,LiveData会立即推送当前持有的值;若后续发生配置变更(如屏幕旋转),Activity重建后再次观察,LiveData会再次推送数据,若DiffUtil逻辑有误,就会出现重复条目。
    • 检查ShopItem的equals/hashCode实现,以及MainActivityDiffUtilCallback的对比逻辑:如果areItemsTheSame或areContentsTheSame判断错误,DiffUtil会误将相同条目识别为新条目,导致重复添加。
    • 若ShopItem的id生成逻辑有问题,会导致sortedSet无法去重,仓库中直接出现重复数据。
  2. 具体修复步骤

    • 规范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添加重复数据。
  3. 验证方式

    • 在仓库的updateShopList方法添加日志,查看shopList内容是否存在重复条目。
    • 在LiveData的观察回调中添加日志,统计回调触发次数及接收的数据内容。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.12 19:40:54