【问题标题】:DiffUtil not refreshing view in Observer call android kotlinDiffUtil 不刷新观察者调用android kotlin中的视图
【发布时间】:2021-12-11 08:54:11
【问题描述】:

嘿,我正在使用带有 ListAdapter 的 diff util。列表的更新有效,但我只能通过滚动列表来查看这些新值,即使不回收视图(滚动时),我也需要查看更新,就像 notifyItemChanged() 一样。我尝试了这个答案ListAdapter not updating item in RecyclerView 中的所有内容,仅对我有用的是 notifyItemChanged 或再次设置 adapter。我正在添加一些代码。请问有人知道如何解决这个问题吗?

数据和枚举类

data class GroupKey(
    val type: Type,
    val abc: Abc? = null,
    val closeAt: String? = null
)

data class Group(
    val key: GroupKey,
    val value: MutableList<Item?> = ArrayDeque()
)

enum class Type{
  ONE,
  TWO
}

data class Abc(
    val qq: String? = null,
    val bb: String? = null,
    val rr: RType? = null,
    val id: String? = null
)

data class RType(
    val id: String? = null,
    val name: String? = null
)


data class Item(
    val text: String? = null,
    var abc: Abc? = null,
    val rr: rType? = null,
    val id: String? = null
)

viewmodel.kt

var list: MutableLiveData<MutableList<Group>?> = MutableLiveData(ArrayDeque())

 fun populateList(){
  // logic to call api 
   list.postValue(data)
 }


 fun addItemTop(){
  // logic to add item on top
  list.postValue(data)
 }

在视图模型内部我通过视图模型函数内部的 api 调用填充数据并将值返回到列表。还有另一个函数将项目插入列表顶部,这就是使用 ArrayDeque

的原因

现在我正在添加嵌套的 reyclerview diff util 回调。

FirstAdapter.kt

class FirstAdapter :
    ListAdapter<Group, RecyclerView.ViewHolder>(comp) {

    companion object {
        private val comp = object : DiffUtil.ItemCallback<Group>() {
            override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
                return oldItem == newItem
            }

            override fun areContentsTheSame(oldItem: Group, newItem: Group): Boolean {
                return ((oldItem.value == newItem.value) && (oldItem.key == newItem.key))
            }
        }
    }
 ......... more function of adapter
}

FirstViewHolder

val adapter = SecondAdapter()
binding.recyclerView.adapter = adapter
adapter.submitList(item.value)

SecondAdapter.kt

class SecondAdapter : ListAdapter<Item, OutgoingMessagesViewHolder>(comp) {

    companion object {
        private val comp = object : DiffUtil.ItemCallback<Item>() {
            override fun areItemsTheSame(oldItem: Item, newItem: Item): Boolean {
                return oldItem.id == newItem.id
            }

            override fun areContentsTheSame(oldItem: Item, newItem: Item): Boolean {
                return ((oldItem.rr == newItem.rr) &&
                        (oldItem.text == oldItem.text) && (oldItem.abc == newItem.abc))
            }
        }
    }
 ..... more function
}

Activity.kt

 viewModel.list.observe(this, { value ->
            submitList(value)
 })

private fun submitList(list: MutableList<Group>?) {
        adapter?.submitList(list)
 //       adapter?.notifyDataSetChanged()
}

我 100% 确定我的列表正在更新,并且当我的新列表添加时我的观察者正在调用。我通过调试视图进行调试。但问题是我只能通过滚动列表来查看这些新值,即使不回收视图(滚动时)我也需要查看更新,就像notifyItemChanged()

更新

viewmodel.kt

class viewModel : BaseViewModel(){
    
 var list: MutableLiveData<MutableList<Group>?> = MutableLiveData()
//... more variables...


 fun fetchData(context: Context) {
        viewModelScope.launch {
            val response = retroitApiCall()
                response.handleResult(
                    onSuccess = { response ->
                                              
                    list.postValue(GroupData(response?.items, context))
                    },
                    onError = { error ->
                       Log.e("error" ,"$error")
                    }
                )
            }
        }
    }

 internal fun GroupData(items: List<CItem>?, context: Context): MutableList<Group> {
        val result: MutableList<Group> = MutableList()

        items?.iterator()?.forEach { item ->
        // adding item in list by add function and then return list.
        return result
    }


    private fun addItemOnTop(text: String) {
         list.value?.let { oldlist ->
          // logic to add items on top of oldlist variable
           if(top != null){
              oldlist.add(0,item)
           }else{
               val firstGroup = oldlist[0]
               firstGroup.value.add(item)
           }
          list.postValue(oldlist)
         }
    }
}

我正在使用 sealed 类这样的东西,但不是这个Example。调用 api Retrofit Example 时类似于这些的东西。这两个链接我都给你举个例子。我在视图模型中使用的内容。

【问题讨论】:

    标签: android android-recyclerview android-adapter android-livedata android-diffutils


    【解决方案1】:

    我不知道发生了什么,但我可以告诉你两件引起我注意的事情。

    第一个适配器:

      override fun areItemsTheSame(oldItem: Group, newItem: Group): Boolean {
          return oldItem == newItem
      }
    

    您不是在比较项目是否相同,而是在比较项目及其内容是否相同。你没有像你在 second 适配器中那样的 ID 吗?

    我可能会检查oldItem.key == newItem.key

    提交列表

    the answer you linked 所示,submitList 有一个非常奇怪的逻辑,它比较实际列表的引用 是否相同,如果相同,则 什么都不做

    在您的问题中,您没有显示列表的来源(通过似乎是 liveData 或 RXJava 的内容观察到),但是构造列表的 souce 是不可见的。

    换句话说:

    // P S E U D O   C O D E
    val item1 = ...
    val item2 = ...
    val list1 = mutableListOf(item1, item2)
    
    adapter.submitList(list1) // works fine
    item1.xxx = ""
    adapter.submitList(list1) // doesn't work well.
    

    为什么?

    不幸的是,submitListsource code 告诉我们,如果对列表的引用相同,则不会计算差异。这实际上不在适配器上,而是在AsyncListDiffer 上,ListAdapter 在内部使用。 differ 负责触发计算。但如果列表引用相同,则不相同,它会默默地忽略它。

    我怀疑您没有创建新列表。这种相当无证和沉默的行为弊大于利,因为通常情况下,开发人员并不期望复制提供给一个对象的列表,该对象的目的和承诺是提供“神奇地"(更重要的是,自动)计算它与前一个之间的差异。

    我理解他们为什么这样做,但我会至少发出一个日志警告,表明您提供的是相同的列表。或者,如果你想避免污染已经被污染的 logCat,那么至少更加明确in its official documentation

    唯一的提示就是这个简单的短语:

    当有新列表可用时,您可以使用submitList(List)

    这里的关键是新列表这个词。所以不是包含新项目的相同列表,而只是一个新的List 引用(无论项目是否相同)。

    你应该尝试什么?

    我会先修改你的 submitList 方法:

    private fun submitList(list: MutableList<Group>?) {
            adapter?.submitList(list.toMutableList())
    }
    

    对于 Java 用户来说:

    adapter.submitList(new ArrayList(oldList));
    

    更改是创建您收到的列表的副本list.ToMutableList()。这样AsyncListDiffer 的列表相等性检查将返回false 并且代码将继续。

    更新/调试

    很遗憾,我不知道您的代码发生了什么;我向您保证ListAdapter 有效,因为我自己每天都在使用它;如果你认为你发现了一个有问题的案例,我建议你创建一个小原型并将其发布到 github 或类似网站上,以便我们重现它。

    我将从在关键区域使用调试/断点开始:

    1. 视图模型;写下您“返回”的列表中的引用。
    2. DiffUtil 方法,是否调用了 diffUtil?
    3. 您的 submitList() 方法,列表引用与您在 ViewModel 中的引用相同吗?

    你需要更深入地挖掘,直到你发现谁没有做什么。

    关于深拷贝和浅拷贝以及 Java 等等......

    请记住,ListAdapter(通过 AsyncDiff)检查对列表的引用是否相同。换句话说,如果您有一个列表 val x = mutableListOf(...) 并将其提供给适配器,它将第一次工作。

    如果您随后修改列表...

    val x = mutableListOf(...)
    adapter.submitList(x)
    
    x.clear()
    adapter.submitList(x)
    

    这将无法正常工作,因为在适配器看来两个列表是相同的(它们实际上是同一个列表)。

    列表是可变的这一事实无关紧要。 (我仍然对可变列表皱眉;为什么submitList 接受一个可变列表,如果你不能改变它并再次提交它,我不知道,但我不会批准这样的拉取请求)如果它会避免大多数问题他们只采用了一个不可变的列表,因此意味着如果您对其进行变异,则每次都必须提供一个新列表。总之……

    正如我所说,复制列表很简单,在 Kotlin 或 Java 中都有多种变体:

    val newListWithSameContents = list1.toList() 
    
    List newListWithSameContents = ArrayList(list1);
    

    现在如果list1 有一个项目...

    list1.add("hello")
    

    当您将 list1 复制到 newList... 对“Hello”(字符串)的引用相同。如果 String 是可变的(它不是,但假设它是),并且您以某种方式修改了该字符串...您将同时修改 both 字符串 或者更确切地说,相同的字符串,两个列表中都引用了

        data class Thing(var id: Int)
    
        val thing = Thing(1)
        val list1: MutableList<Thing> = mutableListOf(thing)
        val list2: MutableList<Thing> = list1.toMutableList()
    
        println(list1)
        println(list2)
    
    // This prints
    [Thing(id=1)]
    [Thing(id=1)]
    

    现在修改东西...

        thing.id = 2
        
        println(list1)
        println(list2)
    

    正如所料,两个列表都指向同一个对象:

    [Thing(id=2)]
    [Thing(id=2)]
    

    这是一个浅拷贝,因为项目没有被拷贝。它们仍然指向内存中相同的thing

    ListAdapter/DiffUtil 不关心对象在这方面是否相同(取决于你如何实现你的 diffutil);但他们肯定关心列表是否相同。如上例所示。

    我希望这能阐明 ListAdapter 调度更新所需的内容。如果没有这样做,请检查您是否有效地做正确的事情。

    【讨论】:

    • 感谢您回复我。我也尝试过这种方式。但它没有用,我更新了 viewmodel 代码。请看一看。我在 viewModel 中添加了一些我正在做的代码。再次感谢。
    • 你检查了吗?
    • 查看更新的答案。
    • 我会在 github 中添加代码,并在准备好时标记你,谢谢
    • 要跟踪两个引用:列表 (1) 和列表中的每个项目 (n)。 AsyncDiff 检查实际列表中的相等性,而不是项目。所以是的,你需要new ArrayList(oldList) 这给你一个新列表的新引用,它的项目与旧列表的引用完全相同;如果您检查oldListnew 列表的引用,您会发现它们是不同的。如果您在任一列表中修改 一个项目,它将在两个列表中都被修改。如果您从一个列表中删除一个项目,它不会从另一个列表中删除
    猜你喜欢
    • 2021-12-10
    • 1970-01-01
    • 2016-03-04
    • 1970-01-01
    • 1970-01-01
    • 2020-11-21
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多