Skip to content

Android RecyclerView DiffUtil — Complete Guide

DodaTech Updated 2026-06-24 2 min read

In this tutorial, you'll learn about Android RecyclerView DiffUtil. We cover key concepts, practical examples, and best practices to help you understand and apply this topic effectively.

The Problem

Your DiffUtil makes items jump around, animations look wrong, or the diff computation blocks the UI thread on large lists.

Wrong Approach ❌

// Diff computed synchronously on the main thread
val result = DiffUtil.calculateDiff(object : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size
    override fun areItemsTheSame(i: Int, j: Int) = oldList[i].id == newList[j].id
    override fun areContentsTheSame(i: Int, j: Int) = oldList[i] == newList[j] // Object reference check!
})
result.dispatchUpdatesTo(adapter)

Output: Items always appear as "changed" because object equality fails.

Right Approach ✅

class UserDiffCallback(
    private val oldList: List<User>,
    private val newList: List<User>
) : DiffUtil.Callback() {
    override fun getOldListSize() = oldList.size
    override fun getNewListSize() = newList.size

    override fun areItemsTheSame(oldPos: Int, newPos: Int): Boolean {
        return oldList[oldPos].id == newList[newPos].id
    }

    override fun areContentsTheSame(oldPos: Int, newPos: Int): Boolean {
        return oldList[oldPos].name == newList[newPos].name &&
                oldList[oldPos].avatarUrl == newList[newPos].avatarUrl
    }
}

// Use AsyncListDiffer for background computation
val differ = AsyncListDiffer(this, object : DiffUtil.ItemCallback<User>() {
    override fun areItemsTheSame(a: User, b: User) = a.id == b.id
    override fun areContentsTheSame(a: User, b: User) = a.name == b.name
})
differ.submitList(newList)

Output: Correct item animations with background diff computation.

Prevention

  • Always use AsyncListDiffer or ListAdapter for background diffing.
  • Compare individual fields in areContentsTheSame, not object references.
  • Return false from areContentsTheSame and use getChangePayload() for partial bind updates.
  • Never call dispatchUpdatesTo on the main thread for lists > 100 items.

Common Mistakes with recyclerview diffutil

  1. Mixing let bindings with <- bindings in do notation, producing type errors
  2. Overlapping type class instances that cause GHC to reject the program with ambiguous dispatch errors
  3. Non-exhaustive pattern matches that compile with warnings then crash at runtime

These mistakes appear frequently in real-world Android code. DodaTech's contributors have identified these patterns through analysis of open-source projects and production systems.

Practice Exercise

Write a pure function that safely divides two integers using Maybe, then test it with edge cases like division by zero and negative numbers.

This exercise reinforces the concepts covered in this guide. Try implementing it before checking online solutions.

FAQ

### What is getChangePayload() used for?

It lets you return a partial payload (like a Bundle or Any) so the Adapter can update only the changed fields in onBindViewHolder(holder, position, payloads), avoiding a full rebind.

### Can DiffUtil handle moves?

Yes. Override areItemsTheSame (true) and areContentsTheSame (false) for a moved item. DiffUtil detects moves when an item exists in both lists but at different positions.

### Why does my list flash when I submit the same data?

If areContentsTheSame returns false for the same data, DiffUtil fires change animations. Make sure you're comparing actual content fields, not just object identity.

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro