Update Android Recyclerview Using Diffutil

If you are an Android Developer, I am sure you’ve used RecyclerView in your android application. In this tutorial, I am going to explain how to update the RecyclerView with DiffUtils. What exactly is RecyclerView? RecyclerView is a more adaptable and efficient version of ListView. It is a container for displaying a larger data set of views that can be recycled and scrolled very quickly.

To learn more about recyclerview, check Recyclerview Android.

What is Recyclerview Diffutil?

There is no way for the RecyclerView to know what the real changes are if notifyDataSetChanged() is used. As a result, all visible views are rebuilt. This is an extremely costly surgery. During this procedure, a new instance of the adapter is generated. As a result, the process is highly time-consuming.

To address this, Android introduced DiffUtils as part of its support library.

DiffUtil is a utility class that can calculate the difference between two lists and output a list of update operations that converts the first list into the second one. It can be used to calculate updates for a RecyclerView Adapter.

Diffutils is based on Eugene Myers’ algorithm.

DiffUtil uses the following methods of the RecyclerViewAdapter:

notifyItemMoved()
notifyItemRangeChanged()
notifyItemRangeInserted()
notifyItemRangeRemoved()
These are less costlier than notifyDataSetChanged since they work on individual operations.

What are the Benefits of using DiffUtil?

Here is the performance chart which illustrates that using DiffUtil is better in the case of RecyclerView. These results are based on Nexus 5X with M-

  • 100 items and 10 modifications: avg: 0.39 ms, median: 0.35 ms
  • 100 items and 100 modifications: 3.82 ms, median: 3.75 ms
  • 100 items and 100 modifications without moves: 2.09 ms, median: 2.06 ms
  • 1000 items and 50 modifications: avg: 4.67 ms, median: 4.59 ms
  • 1000 items and 50 modifications without moves: avg: 3.59 ms, median: 3.50 ms
  • 1000 items and 200 modifications: 27.07 ms, median: 26.92 ms
  • 1000 items and 200 modifications without moves: 13.54 ms, median: 13.36 ms

Creating a DiffUtil class

To create a Recyclerview Diffutil class, Need to extend the Diffutil.CallBack class.

class UserDiffUtilCallback(private val oldList: List<Users>, private val newList: List<Users>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

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

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return when {
            oldList[oldItemPosition].id == newList[newItemPosition].id -> true
            oldList[oldItemPosition].name == newList[newItemPosition].name -> true
            else -> false
        }
    }

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        return super.getChangePayload(oldItemPosition, newItemPosition)
    }
    
}

Diffutil.CallBack the class has the following methods that need to be implemented:

  • getOldListSize() : It returns the size of the old list.
  • getNewListSize() : Returns the size of the new list.
  • areItemsTheSame(int oldItemPosition, int newItemPosition) : We check if the individual items of the list are same. This can be done by checking their ids.
  • areContentsTheSame(int oldItemPosition, int newItemPosition): This checks if the contents of the List data are the same. This method is called by DiffUtil only if areItemsTheSame returns true.
  • getChangePayload(int oldItemPosition, int newItemPosition) : If areItemTheSame return true and areContentsTheSame returns false DiffUtil calls this method to get a payload about the change. Here we can detect if any particular field of the data is changed. We can then pass that changed value using Bundle. It’ll be received in our RecyclerView Adapter class.

Using DiffUtil in RecyclerView Adapter

I have explained about the Recyclerview In another tutorial, So I am directly moving on to the setup of the DiffUtil on the recyclerview adapter.

Now, to make use of this, we replace the set data in Adapter,

fun setUserList(updatedUserList: List<Users>) {
        val diffResult = DiffUtil.calculateDiff(UserDiffUtilCallback(userList, updatedUserList))
        userList.clear()
        userList.addAll(updatedUserList)
        diffResult.dispatchUpdatesTo(this)
    }

Here, we pass both the old and new lists in UserDiffUtilCallback and then we calculate the difference. After we know the difference, we update the user list and notify the adapter via dispatchUpdatesTo.

dispatchUpdatesTo(this) invokes the adapter and informs it about the views to be updated. Next, we copy the new Data list to the data.

Let’s see the Recyclerview diffutil example code,

Recyclerview DiffUtil Example

MainActivity.kt

class MainActivity : AppCompatActivity() {

    lateinit var recyclerView: RecyclerView
    val users = listOf<Users>(
        Users(1, "User1", "location1", "image"),
        Users(2, "User2", "location2", "image"),
        Users(3,"User3","location3","image"),
        Users(4,"User4","location4","image"),
        Users(5,"User5","location5","image")
    )

    val userAdapter = UserAdapter(users.toMutableList())
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        recyclerView = findViewById(R.id.recyclerview)
        recyclerView.adapter = userAdapter



        val btn = findViewById<Button>(R.id.btnSortDescending)
        btn.setOnClickListener {
            val sortedList  = users.sortedByDescending { it.id }
            userAdapter.setUserList(sortedList)
        }
        val btnAscending = findViewById<Button>(R.id.btnAscending)
        btnAscending.setOnClickListener {
            val sortedList  = users.sortedBy { it.id }
            userAdapter.setUserList(sortedList)
        }

    }
}

Model.kt

data class Users(val id: Int, val name: String, val address: String, val image: String)

UserAdapter.kt

class UserAdapter(private val userList: MutableList<Users>): RecyclerView.Adapter<UserAdapter.UserViewHolder>() {

    fun setUserList(updatedUserList: List<Users>) {
        val diffResult = DiffUtil.calculateDiff(UserDiffUtilCallback(userList, updatedUserList))
        userList.clear()
        userList.addAll(updatedUserList)
        diffResult.dispatchUpdatesTo(this)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): UserViewHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.adapter_user, parent, false)
        return UserViewHolder(view)
    }

    override fun onBindViewHolder(holder: UserViewHolder, position: Int) {
        val user = userList[position]
        holder.username.text = user.name
        holder.address.text = user.address
    }

    override fun getItemCount(): Int {
        return userList.size
    }
    inner class UserViewHolder(view: View): RecyclerView.ViewHolder(view) {

        val username: AppCompatTextView = view.findViewById(R.id.username)
        val address: AppCompatTextView = view.findViewById(R.id.address)

    }
}

UserDiffUtilCallback.kt

class UserDiffUtilCallback(private val oldList: List<Users>, private val newList: List<Users>) : DiffUtil.Callback() {
    override fun getOldListSize(): Int {
        return oldList.size
    }

    override fun getNewListSize(): Int {
        return newList.size
    }

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

    override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
        return when {
            oldList[oldItemPosition].id == newList[newItemPosition].id -> true
            oldList[oldItemPosition].name == newList[newItemPosition].name -> true
            else -> false
        }
    }

    override fun getChangePayload(oldItemPosition: Int, newItemPosition: Int): Any? {
        return super.getChangePayload(oldItemPosition, newItemPosition)
    }

}

demo:

This brings an end to this tutorial. You can download the project from the GITHUB.

Leave a Reply

Your email address will not be published.