Android DiffUtil Doesn't Notify Properly

Viewed 53

I was following along the Android Room With a View tutorial.

The tutorial makes use of DiffUtil to compute changes in the list and update the RecyclerView accordingly.

However, when removing or adding items to the RecyclerView, DiffUtil always causes the entire RecyclerView to reload, instead of calling the correct notifyItemRemoved or notifyItemInserted.

My Adapter:

class MarksAdapter(private val context: Context) :
    ListAdapter<Mark, MarksAdapter.MarkViewHolder>(MarksComparator()) {

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MarkViewHolder {
        return MarkViewHolder.create(parent)
    }

    override fun onBindViewHolder(holder: MarkViewHolder, position: Int) {
        val mark = getItem(position)
        holder.bind(context, mark)
    }

    class MarkViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        private var mark: Mark? = null

        fun bind(context: Context, mark: Mark) {
            this.mark = mark
            // Removed for brevity...
        }

        companion object {
            fun create(parent: ViewGroup): MarkViewHolder {
                val view: View =
                    LayoutInflater.from(parent.context).inflate(R.layout.card_view, parent, false)
                return MarkViewHolder(view)
            }
        }
    }

    class MarksComparator : DiffUtil.ItemCallback<Mark>() {
        override fun areItemsTheSame(oldItem: Mark, newItem: Mark): Boolean {
            return oldItem === newItem
        }

        override fun areContentsTheSame(oldItem: Mark, newItem: Mark): Boolean {
            return oldItem == newItem
        }

    }
}

From the activity/fragment:

override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)

        binding.recyclerViewMarks.apply {
            marksAdapter = MarksAdapter(context)
            adapter = marksAdapter
            layoutManager = LinearLayoutManager(this@MarksOverviewFragment.requireContext())
        }

        marksViewModel.allMarks.observe(viewLifecycleOwner) { marks ->
            marks?.let { marksAdapter.submitList(it) }
        }
    }
1 Answers

After thinking about it some more, the only place where I can actually influence what DiffUtil does, is within the DiffUtil.Callback.

So after changing:

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
    return newList[newItemPosition] === oldList[oldItemPosition]
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
    return newList[newItemPosition] == oldList[oldItemPosition]
}

to:

override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
    return newList[newItemPosition].Uid == oldList[oldItemPosition].Uid
}
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
    return newList[newItemPosition] == oldList[oldItemPosition]
}

(notice the comparison of Uid instead of identity)

...the DiffUtil works as expected.

What I'm not sure however, is why it worked in the tutorial, but not for me.

Related