【问题标题】:ListAdapter DiffUtils newItem and oldItem the same when submitList() calledListAdapter DiffUtils newItem 和 oldItem 在 submitList() 调用时相同
【发布时间】:2022-02-04 20:46:01
【问题描述】:

仅供参考,我并不是在寻找“修复”,而是寻求解释和讨论,这可能有助于更多地了解这些看似愚蠢的事情是如何运作的。

当我意识到某个地方的某个列表没有正确更新时,我正在从事这个更大的项目。仔细观察,项目正在被正确修改,如果您“滚动离开”并返回,项目的信息将正确显示。

我偶然发现了这篇文章: ListAdapter not updating item in RecyclerView

但这里的不同之处在于,实际上调用了 DiffUtils,但不知何故 newItemoldItem 是相同的!我了解该库假设您使用的是 Room 或任何其他 ORM,每次更新时都会提供一个新的异步列表,但这就是问题所在。如果我“天真”地提交列表,则甚至不会调用 DiffUtils。但是,如果我像某些人建议的那样以list.toMutableList() 提交列表,则会调用 DiffUtils,但不知何故,新旧项目已经相同,因此,此时没有任何更新(通过在 @ 中放置断点来验证这一点987654326@).

我将相关的 sn-ps 和一个指向我创建的测试项目的链接留在这里,以便我可以封装行为并将其与其他所有内容分开测试。

片段 - 只是调用 submitList

viewModel.items.observe(viewLifecycleOwner) {
    adapter.submitList(it.toMutableList())
}

视图模型

private val _items = MutableLiveData<List<SimpleItem>>()
val items: LiveData<List<SimpleItem>>
    get() = _items

init {
    _items.value = ItemsRepo.getItems()
}

fun onItemClick(itemId: Int) {
    ItemsRepo.addItemCount(itemId)
    _items.value = ItemsRepo.getItems()
}

“回购”我创建了一些数据 对象项目回购 {

private var items = mutableListOf(
    SimpleItem(1),
    SimpleItem(2),
    SimpleItem(3),
    SimpleItem(4),
    SimpleItem(5)
)

fun getItems(): List<SimpleItem> {
    return items
}

fun addItemCount(itemId: Int) {
    items.find { it.itemId == itemId }?.let {
        it.itemClickCount += 1
    }
}

GitHub 存储库: https://github.com/ellasaro/ListAdapterTest

干杯!

【问题讨论】:

    标签: android kotlin android-recyclerview listadapter android-diffutils


    【解决方案1】:

    不要在 DiffUtil 中使用可变数据类或可变列表。它可能导致各种问题。 DiffUtil 依赖于比较两个列表,因此如果其中一个是可变的并且已更改,则无法成功比较新旧,因为没有先前状态的记录。

    我没有花时间缩小您的确切问题范围,但我敢打赌,如果您将回购的 getItems() 更改为返回 items.toList()(因此改变回购不会改变下游列表),并将 SimpleItem 更改为成为一个不可变的类,你的问题就会消失。

    不幸的是,使 SimpleItem 不可变会有点麻烦。点击监听器而不是改变项目将必须向 repo 报告已更改项目的 id,并且 repo 必须手动将其换出,然后刷新列表。

    如果您的 Repo 返回一个在向其报告更改时自动发出的列表流,它将更清晰。这样您的 ViewModel 就不必同时报告更改,然后记住再次手动查询列表状态。

    我会使用toList() 而不是toMutableList()。可变列表表明您计划改变列表而不是仅仅读取它,您绝不能将列表传递给 DiffUtil。

    【讨论】:

    • 你是 100% 正确的。我已经尝试过:如果您的 Repo 返回一个在报告更改时自动发出的列表流并且它有效,但我试图弄清楚为什么这种其他方法没有。问题确实似乎是这一切的可变性。使 SimpleItem 的点击计数不可变,并从 getList() 返回 List 效果很好。我的适配器的点击处理程序已经返回了项目的id,所以我搜索了元素的索引并将其替换为修改后的副本。我现在了解(某种程度上)问题的原因。感谢您的洞察力!
    • 事后想到,当您创建 Room 数据库实体时,无论它们的属性是 var 还是 val,这个问题似乎都不会发生。对此有什么想法吗?列表的可变性而不是类的可变性似乎确实是一个问题。如果我将itemClickCount 属性保留为var 但完全替换元素并提交更新的不可变列表,它仍然有效。只是想了解不同的可能场景。
    • 当然,Room 每次生成新的返回结果时,都会将最新的数据重新解析为新实例化的数据类实例,并将它们放入一个全新的 List 实例中。另一种方法是查看这些属性是否可变的复杂性,如果是,则缓存之前返回的项目并对其进行变异,以产生巨大的代码气味和模棱两可的行为。
    【解决方案2】:

    itemClickCount 属性声明为val,并将列表作为不可变列表从 Repo 对象中得到,就像Tenfour04 建议的那样。

    作为补充观察,如果将itemClickCount 属性保留为var 但完全替换元素并重新提交更新的列表,则它可以正常工作。所以问题似乎是直接在 Repo 的列表中修改对象的可变属性。在这种情况下,在 getList() 中使用 .toList() 并没有帮助。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-03
      • 2021-11-28
      • 1970-01-01
      • 1970-01-01
      • 2021-10-12
      • 2021-03-28
      • 2021-05-23
      • 1970-01-01
      相关资源
      最近更新 更多