具有DiffUtil.ItemCallback的ListAdapter始终将对象视为相同 [英] ListAdapter with DiffUtil.ItemCallback always considers objects the same
问题描述
我正在尝试将ListAdapter与Room和LifeData一起使用.但是我遇到了DiffUtil.ItemCallback的奇怪行为-areContentsTheSame()方法中的对象总是相同的. 添加和删除对象没有问题,但是更改内容没有问题.
物品类别:
@Entity(tableName = "item")
data class Item(var num: Int) {
@PrimaryKey(autoGenerate = true)
var key: Int = 0
}
适配器类
class LifeAdapter : ListAdapter<Item, LifeAdapter.ViewHolder>(DiffCallback()) {
private class DiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item) = oldItem.key == newItem.key
override fun areContentsTheSame(oldItem: Item, newItem: Item) = oldItem.num == newItem.num
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
val position = holder.layoutPosition
holder.bind(getItem(position))
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Item) {
itemView.findViewById<TextView>(R.id.txt_num).text = item.num.toString()
itemView.findViewById<TextView>(R.id.txt_key).text = item.key.toString()
}
}
}
活动分类:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dao = getDao(this)
val data = dao.getAllItems()
val adapter = LifeAdapter()
rv.layoutManager = LinearLayoutManager(this)
rv.adapter = adapter
val nameObserver = Observer<List<Item>> { adapter.submitList(it) }
data.observe(this, nameObserver)
btn_add.setOnClickListener {
val item = Item(Random.nextInt(0, 1000))
runAsync { dao.insertItem(item) }
}
btn_change.setOnClickListener { v ->
data.value.let {
if (it!!.isNotEmpty()) {
it[0].num = 111
runAsync { dao.updateItem(it[0]) }
}
}
}
btn_delete.setOnClickListener { v ->
data.value.let {
if (it!!.isNotEmpty()) {
runAsync { dao.deleteItem(it[0]) }
}
}
}
}
}
完整项目- https://yadi.sk/d/7tpzDhUA-udoIQ >
视频- https://youtu.be/PZYeAfGzXBg
问题出在方法areContentsTheSame()中的LifeAdapter.DiffCallback类中. 如果项目内容(数字)发生变化,则在此方法中,newItem和oldItem相同并且等于新项目:
这意味着方法areContentsTheSame()始终返回true. 我通过链接检查了相等性(newItem === oldItem),它总是应该是虚假的. 我不明白怎么了.通过adapter.submitList()方法添加新列表时,newItem和oldItem必须不同.
由于LiveData
返回的结果相同 List
,因此您必须创建一个新的.
这是使用toList()
的原始答案的简短答案.
recycler.observe(this, Observer{
adapter.submitList(it.toList())
})
如果您更喜欢使用kotlin扩展名,则可以执行以下操作:
fun <T> MutableLiveData<List<T>>.add(item: T) {
val updatedItems = this.value?.toMutableList()
updatedItems?.add(item)
this.value = updatedItems
}
这样,您不必添加toList()
,而只需使用扩展名.
I`m trying to use ListAdapter with Room and LifeData. But i faced strange behavior of DiffUtil.ItemCallback - objects are always the same in areContentsTheSame() method. No problem with adding and removing object, but problem with changing the content.
Item class:
@Entity(tableName = "item")
data class Item(var num: Int) {
@PrimaryKey(autoGenerate = true)
var key: Int = 0
}
Adapter class
class LifeAdapter : ListAdapter<Item, LifeAdapter.ViewHolder>(DiffCallback()) {
private class DiffCallback : DiffUtil.ItemCallback<Item>() {
override fun areItemsTheSame(oldItem: Item, newItem: Item) = oldItem.key == newItem.key
override fun areContentsTheSame(oldItem: Item, newItem: Item) = oldItem.num == newItem.num
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val view = LayoutInflater.from(parent.context).inflate(R.layout.item_item, parent, false)
return ViewHolder(view)
}
override fun onBindViewHolder(holder: ViewHolder, pos: Int) {
val position = holder.layoutPosition
holder.bind(getItem(position))
}
class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
fun bind(item: Item) {
itemView.findViewById<TextView>(R.id.txt_num).text = item.num.toString()
itemView.findViewById<TextView>(R.id.txt_key).text = item.key.toString()
}
}
}
Activity class:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val dao = getDao(this)
val data = dao.getAllItems()
val adapter = LifeAdapter()
rv.layoutManager = LinearLayoutManager(this)
rv.adapter = adapter
val nameObserver = Observer<List<Item>> { adapter.submitList(it) }
data.observe(this, nameObserver)
btn_add.setOnClickListener {
val item = Item(Random.nextInt(0, 1000))
runAsync { dao.insertItem(item) }
}
btn_change.setOnClickListener { v ->
data.value.let {
if (it!!.isNotEmpty()) {
it[0].num = 111
runAsync { dao.updateItem(it[0]) }
}
}
}
btn_delete.setOnClickListener { v ->
data.value.let {
if (it!!.isNotEmpty()) {
runAsync { dao.deleteItem(it[0]) }
}
}
}
}
}
Full project - https://yadi.sk/d/7tpzDhUA-udoIQ
Video - https://youtu.be/PZYeAfGzXBg
The problem is in LifeAdapter.DiffCallback class in method areContentsTheSame(). If item content (num) changing, in this method both newItem and oldItem are the same and equals to new item:
That meant the method areContentsTheSame() always return true. I checked equality by link (newItem === oldItem) and it always false as it should be. I can`t understand what is wrong. newItem and oldItem must be different when new List added via adapter.submitList() method.
Since LiveData
returns the same List
you have to create a new one.
Here is a shorter answer to the original answer by using toList()
.
recycler.observe(this, Observer{
adapter.submitList(it.toList())
})
If you rather use a kotlin extension you can do something like this:
fun <T> MutableLiveData<List<T>>.add(item: T) {
val updatedItems = this.value?.toMutableList()
updatedItems?.add(item)
this.value = updatedItems
}
That way you don't have to add the toList()
and just use the extension.
这篇关于具有DiffUtil.ItemCallback的ListAdapter始终将对象视为相同的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!