DiffUtil 不刷新观察者调用android kotlin中的视图
Posted
技术标签:
【中文标题】DiffUtil 不刷新观察者调用android kotlin中的视图【英文标题】:DiffUtil not refreshing view in Observer call 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 时类似于这些的东西。这两个链接我都给你举个例子。我在视图模型中使用的内容。
【问题讨论】:
【参考方案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.
为什么?
不幸的是,submitList
的source 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 或类似网站上,以便我们重现它。
我将从在关键区域使用调试/断点开始:
-
视图模型;写下您“返回”的列表中的引用。
DiffUtil 方法,是否调用了 diffUtil?
您的
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)
这给你一个新列表的新引用,它的项目与旧列表的引用完全相同;如果您检查oldList
和new
列表的引用,您会发现它们是不同的。如果您在任一列表中修改 一个项目,它将在两个列表中都被修改。如果您从一个列表中删除一个项目,它不会从另一个列表中删除。以上是关于DiffUtil 不刷新观察者调用android kotlin中的视图的主要内容,如果未能解决你的问题,请参考以下文章
Android 高性能列表:RecyclerView + DiffUtil
Android简易音乐重构MVVM Java版-使用DiffUtil解决recycleView整体数据刷新性能问题(二十二)
Android简易音乐重构MVVM Java版-使用DiffUtil解决recycleView整体数据刷新性能问题(二十二)