14

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:

enter image description here

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.

4 Answers 4

14

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.

Sign up to request clarification or add additional context in comments.

5 Comments

I'm having this same issue as described on the question here. I tried your suggestion and it didn't work forme. Looks like the list being passed to submitList() is never the same, so using your suggestion didn't change that.
The list is always the same, the content isn't so that's why you add the .toList().
Actually it turned out I was updating the LiveData object manually, and then afterwards submitList was being called. As I updated the LiveData object, the UI got updated first. So a few secs later when submitList was called, the lists were already the same.
@leoneboaventura How did you fix it ?
@leoneboaventura i am also facing the same problem, can you please share your solution?
7

Kotlin with Data Classes

adapter.submutList(list.map { it.copy() })

4 Comments

Where is that list comming from? it has an unresolved reference to copy()
@FelixPK It should be a list of data class. Data Classes has a copy function that creates the same object, but the reference in its memory is different.
Thanks. i have spent a lot of time to solve, why does not update ui problem.
I really don't understand wtf is going on in my code here but this has fixed it :D
3

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)
    }

Comments

0

Replace this code:

btn_change.setOnClickListener { v ->
            data.value.let {
                if (it!!.isNotEmpty()) {
                    it[0].num = 111
                    runAsync { dao.updateItem(it[0]) }
                }
            }
        }

with this:

btn_change.setOnClickListener { v ->
            data.value.let {
                if (it!!.isNotEmpty()) {
                    runAsync { dao.updateItem(it.set(0, Item(111))) }
                }
            }
        }

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.