【问题标题】:DiffUtil ItemCallback areContentsTheSame() always returns true after updating an item on the ListAdapterDiffUtil ItemCallback areContentsTheSame() 在更新 ListAdapter 上的项目后始终返回 true
【发布时间】:2020-01-12 12:36:50
【问题描述】:

在使用 ListAdapter 时,我注意到在更新项目后 DiffUtil.ItemCallback areContentsTheSame() 方法总是返回 true。调试代码我发现旧的和新的项目是完全一样的(旧的状态消失了)。因此,ListAdapter onBindViewHolder() 方法没有被调用来更新列表中的相应行(只有项目的顺序随着漂亮的动画而改变)。

查看 Stackoverflow 上的其他问题似乎是许多开发人员面临的常见问题:

ListAdapter not updating item in reyclerview

ListAdapter not updating when editing content

DiffUtil.Callback not working as expected

Items in Recyclerview + Listadapter won't redraw on update

ListAdapter with DiffUtil.ItemCallback always considers objects the same

When submitting a new list to RecyclerView ListAdapter the diff check always returns true for areContentsTheSame()

但是,上述答案(如果有的话)都没有提供正确的解决方案。

它对我有用的唯一方法是每次观察者发出新结果时在 ListAdapter 上调用 notifyDataSetChanged()

但是,如果通过强制 RecyclerView 重绘其内容(它拥有的所有项目)而放弃了您可以获得的所有出色性能,那么使用 ListAdapter 和 submitList() 通知更改的全部意义是什么?显示到目前为止)?

  1. notifyDataSetChanged():

即使它有效,但如果您决定首先使用 ListAdapter,肯定不是正确的方法。

  1. viewModel.getObjects().observe(this, listOfObjects -> listAdapter.submitList(new ArrayList<>(listOfObjects)));:

没用。

更新列表中的项目后,我希望看到 UI 上相应行发生的相应更改(不仅是排序顺序)。

【问题讨论】:

  • 同样的问题,你找到解决办法了吗?
  • @Nux 不幸的是还没有。
  • @fmiralles 怎么样了?面临同样的问题((
  • 你是如何更新你的对象的?你能分享你的代码吗

标签: android android-recyclerview listadapter android-diffutils


【解决方案1】:

我不知道这是否是针对这种特定情况的解决方案,但根据描述,这听起来与我最近经历的一模一样。

我第一次使用新的 Room + LiveData + ViewModel + DiffUtils 集成,并且在更新列表后更新 RecyclerView 中的项目时遇到了同样的问题。

我遇到的问题是由于我对更新列表并允许 DiffUtils 完成其应有的工作的理解。希望以下内容足够清楚:

我在做什么:

  1. 用户使用对话框更新其项目
  2. RecyclerView 中的列表项已更新为新信息
  3. 房间触发 LiveData 观察者,因为可能有 改变了
  4. RecyclerView DiffUtils 尝试检查两者之间的任何差异 旧适配器列表和新适配器列表
  5. 未检测到任何新内容
  6. 适配器未触发更新其 UI

我的错误是认为问题出在 .5 中,这导致我花了半天时间来回调试问题。最终,我偶然发现了一个 SO 问题(目前找不到),这使我找到了问题的正确根源。这个问题确实存在于 .2 中 - 更新列表中的项目。

这是问题所在,因为我们甚至在 DiffUtils 有机会比较新旧列表更改之前就使用新更改更新我们的 CURRENT 适配器列表。这意味着每次 DiffUtils 总是将列表与已包含新列表所做更改的旧列表进行比较。

解决方案?不要更新列表项,因为它是一个对象,并且列表对象保持相同的引用,从而导致在使用/引用它的任何地方都更新相同的实例。 而是克隆项目对象(如在深度克隆中,我知道这可能很烦人),应用用户对克隆对象所做的更改,然后使用该克隆更新房间数据库中的项目条目。 不要用克隆替换适配器列表项,不要管原来的,我们只想更新数据库中的信息,而不是列表,因为 DiffUtils 会处理这个。

我们在这里所做的基本上是为我们的房间数据库创建一个更新负载,然后触发 LiveData 观察器将旧列表与新列表(包含更新的项目数据)进行比较,从而在两者之间进行预期的更改检测列表。


简而言之;

这样做:

  • 深度克隆适配器列表项
  • 仅使用新信息更新克隆
  • 使用克隆信息更新数据库

不要这样做:

  • 直接更新适配器列表项
  • 使用列表项信息更新数据库

【讨论】:

    【解决方案2】:

    几天前,我遇到了同样的问题。我试图像这样更新适配器内的对象:

    binding.accept.setOnClickListener {
                existingEntity.taskStatus = "accept"
                listener.onItemClick(existingEntity)
            }
    

    上面的代码正在更新数据库对象,但没有反映在我的 recyclerview 上。这是因为对象的相同引用而发生的。因此,当我在适配器中更新对象时,它会自动更新列表中的对象。所以我改变了我的代码如下:

    binding.accept.setOnClickListener {
                val newEntity = existingEntity.copy()
                newEntity.taskStatus = "accept"
                listener.onItemClick(newEntity)
            }
    

    所以我使用 Kotlin 数据类 copy() 方法创建了一个复制对象,它对我有用。

    【讨论】:

    • 你拯救了我的一天。准备好伟人
    【解决方案3】:

    我花了很多时间才发现我遇到了与本文所述相同的问题。我尝试了很多解决方案,但最后我意识到我在不知不觉中搞砸了。如果您遇到同样的问题,请检查您是否没有先更新 UI 并调用写入命令来更新存储库中的值。 就我而言,我正在更新 LiveData 对象,然后在 Firebase 数据库上更新该值。这个问题是因为我的 UI 正在监听那个 LiveData 对象的变化而引起的,所以我的 Firebase 数据库观察者检测到了变化,调用了 ListAdapter 并且看起来没有任何变化。

    为了解决这个问题,我删除了更新 LiveData 对象的行,只调用 Firebase 数据库发送更改。

    【讨论】:

      【解决方案4】:

      我的声望为 0,所以我无法发表评论,但 @Shadow 的回答对我有用。

      我还在使用 Room + LiveData + ViewModel + DiffUtil 并使用对话框编辑列表项的内容。尽管 java 是按值传递的,但当我们传递对象的值时,实际上是在传递对它的引用。因此,当您在对话框中编辑同一对象的内容时,您实际上是在更改列表项的相同引用,因此当 LiveData 执行 onChange 时,为什么 DiffUtil 无法计算差异。

      这更好地解释了 java 通过值/引用传递: Is Java "pass-by-reference" or "pass-by-value"?

      我做了什么:

      Item newItem = new ItemBuilder().setId(oldItem.getId()).......

      然后对 newItem 进行更改,然后从 viewModel 更新数据库

      【讨论】:

        【解决方案5】:

        我最近也面临这个问题。对于部分 ViewHolder 更改,我的方法是这样的:

        • 当在适配器的列表项中检测到点击时 -> 通过回调将其委托给您的视图

        • view 会将此点击委托给具有点击位置的 ViewModel。

        • ViewModel 将更新该位置的对象属性。就我而言,我有一个List<FeedPost> list。每个FeedPost 都有用户信息、连接按钮、喜欢计数等。所以如果用户点击喜欢按钮,我会在我的 ViewModel 中增加它的值,比如list.get(clickedPos).incrementLikeByOne()

        • 现在更新 ViewModel 中的列表也将反映适配器列表中的更改,因为适配器列表本质上包含相同的对象引用。当您执行add()addAll() 时,您并没有制作列表项的深层副本。这就是 DiffUtil 无法检测到部分视图持有者更改的原因。

        • 我所做的是创建一个HashMap<Integer, Object> changeDetailsMap。此映射将包含其值发生更改的数据,键可以是位置,并在 areContentsTheSame() 中使用此映射返回 true/false 以触发部分 ViewHolder 更改。我使用对象的原因是我可以传递我想要的任何东西(整数、字符串或我自己的 POJO),但你必须注意在 DiffUtil 中正确地转换它。确保您在此 HashMap 中放入的任何对象都有一个 int 字段(假设为 int classType,以便您可以将其转换为正确的 Class)

        • 这里要注意的一点是,您必须像这样将它与SingleLiveEvent 一起使用:SingleLiveEvent<HashMap<Integer, String>> changeDetailsMap_SLE,因为它是一次性操作,而不是 MutableLiveData,因为它本质上是粘性的。我正在使用片段,所以当片段从后台堆栈中弹出时,普通 LiveData 再次调度最近的值,因为当片段从后台堆栈中出来时它变得活跃。如果您不知道,您可以谷歌 SingleLiveEvent。您可以将其用于一次性操作。虽然,一位谷歌员工在他的中篇文章中提出了一种更好的方法,但实际上我发现这个 SingleLiveEvent 很容易使用。

        • 推送此哈希图以查看:changeDetailsMap_SLE.setValue(changeDetailsMap)。您可以像普通 LiveData 一样观察此 SingleLiveEvent,并将其传递给您的适配器并将其分派给您的 DiffUtil。我在适配器中这样做的方式是这样的

           public void updateConnectionStatus(Map<String, String>) {
               List<FeedAdapterModel> pseudoNewList = new ArrayList<>(adapterList); //adapterList is the list which you already have in your adapter class
               DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new FeedDiffUtil(adapterList, pseudoNewList, updatedConnectionStatusMap));
               adapterList.clear();
               adapterList.addAll(pseudoNewList);
               diffResult.dispatchUpdatesTo(this);
           }
          
        • 另外,不要忘记在适配器中完成工作时清除此哈希图。当您再次推送此 SingleLiveEvent 时,您不希望获得之前运行 DiffUtil 的旧位置。

        这种方法也适用于您使用equals() 方法比较列表项(在areItemsTheSame() 内)而不使用像oldList.get(oldItemPosition).getPostId() == newList.get(newItemPosition).getPostId() 这样的对象属性的情况。在我的情况下,我有一个异构的adapterList,因此并非所有列表项都保证具有postID,因此我不能依赖对象属性比较,因为它可以为null。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2020-06-04
          • 2023-04-02
          • 2011-05-09
          • 1970-01-01
          • 1970-01-01
          • 2023-03-27
          • 1970-01-01
          相关资源
          最近更新 更多