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.
LiveData returns the same instances in the List.
Solution I found - create new List with copy of Items:
val nameObserver = Observer<List<Item>> {
val newList = mutableListOf<Item>()
it.forEach { item -> newList.add(item.copy()) }
adapter.submitList(newList)
}
Kotlin with Data Classes
adapter.submutList(list.map { it.copy() })
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With