The powerful tool DiffUtil in RecyclerView - Android Tutorial

Ever built a List in Android ? What did you use for it? ListView or RecyclerView. If you are living in 2019, I am pretty sure you might have used RecyclerView. In this blog, we will talk more about updating the recyclerview using DiffUtils

What is RecyclerView ?

RecyclerView is flexible and efficient version of ListView. It is an container for rendering larger data set of views that can be recycled and scrolled very efficiently.

Before discussing Diff Util, let's discuss the RecyclerView Implementation,

Lets create an Activity MainActivity and add the following in the activity_main.xml file,

<androidx.constraintlayout.widget.ConstraintLayout
 xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        xmlns:app="http://schemas.android.com/apk/res-auto"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        tools:context=".MainActivity">

    <androidx.recyclerview.widget.RecyclerView
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:id="@+id/recycler_view"
            app:layout_constraintBottom_toBottomOf="parent"
            app:layout_constraintLeft_toLeftOf="parent"
            app:layout_constraintRight_toRightOf="parent"
            app:layout_constraintTop_toTopOf="parent"/>

</androidx.constraintlayout.widget.ConstraintLayout>

Now, lets create a data model class and a Data Source ,

data class Rating(val rateIndex: Int, val ratingValue: Int, val name: String)

and the data source looks like,

object DataSource {

    val ratingList: List<Rating>
        get() {
            val ratings = ArrayList<Rating>()
            ratings.add(Rating(1, 10, "Mindorks"))
            ratings.add(Rating(2, 12, "AfterAcademy"))
            ratings.add(Rating(3, 5, "blog.mindorks.com"))
            return ratings
        }
}

Now, to set the list in RecyclerView, lets create an adapter,

class RatingAdapter : RecyclerView.Adapter<RatingAdapter.ViewHolder>() {

    private val ratings = ArrayList<Rating>()


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

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        val rating = ratings[position]
        holder.name_text.text = rating.name
    }

    fun setData(ratings: List<Rating>) {
       ratings.clear()
       ratings.addAll(ratings)
       
    }

    override fun getItemCount(): Int {
        return ratings.size
    }

    class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

        val name_text: TextView = itemView.findViewById(R.id.name_text)

    }
}

and now, let's supply the data from DataSource, in MainActivity,

private fun setList() {
    val ratingAdapter = RatingAdapter()
    recycler_view.layoutManager = LinearLayoutManager(this)
    recycler_view.itemAnimator = DefaultItemAnimator()
    recycler_view.adapter = ratingAdapter
    ratingAdapter.setData(DataSource.ratingList)

}

and after running the app, we will see the List in the MainActivity.

Now, what if we have to update the list with new set of data ?

We will call the,

adapter.setData(/** new data**/)

and call from MainActivity,

adapter.notifyDataSetChanged()

This will update the recyclerview with the new set of data.

But if notifyDataSetChanged was doing the work for you, why was DiffUtils was need?

Let's discuss that now,

  • Using notifyDataSetChanged() , there is no way for the RecyclerView to know what the actual changes are there. So, all the visible views are recreated again. This is a very expensive operation.
  • In this process, the new instance is created of the adapter. Which makes the process very heavy.

To over come this, Android Launched DiffUtils as part of 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. DiffUtils is based on Eugene Myers’ algorithm.
  • DiffUtils is used to know the updats made in the RecyclerView Adapter.

DiffUtil uses these methods to notify the RecyclerView for any changes in the data set:

Compared to notifyDataSetChanged() , these methods are far more efficient.

But to make DiffUtils working in the project, we have to pass the information regarding the old and new list. This is done using DiffUtil.Callback .

We will create a class,

class RatingDiffCallback(private val oldList: List<Rating>, private val newList: List<Rating>) : DiffUtil.Callback() {

    override fun getOldListSize(): Int = oldList.size

    override fun getNewListSize(): Int = newList.size

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

    override fun areContentsTheSame(oldPosition: Int, newPosition: Int): Boolean {
        val (_, _, name) = oldList[oldPosition]
        val (_, _, name1) = newList[newPosition]

        return name == name1
    }

    @Nullable
    override fun getChangePayload(oldPosition: Int, newPosition: Int): Any? {
        return super.getChangePayload(oldPosition, newPosition)
    }
}

Here,

getOldListSize() : It returns the size of the old list.

getNewListSize() : Returns the size of the new list

areItemsTheSame( oldPosition:Int , newPosition:Int) : Called by the DiffUtil to decide whether two object represent the same Item.

areContentsTheSame( oldPosition:Int, newPosition:Int) : Checks whether two items have the same data.You can change its behavior depending on your UI. This method is called by DiffUtil only if areItemsTheSame returns true.

getChangePayload(oldPosition:Int , newPosition:Int) : If areItemTheSame return true and areContentsTheSame returns false DiffUtil calls this method to get a payload about the change.

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

fun setData(newRating: List<Rating>) {
    val diffCallback = RatingDiffCallback(ratings, newRating)
    val diffResult = DiffUtil.calculateDiff(diffCallback)
    ratings.clear()
    ratings.addAll(rating)
    diffResult.dispatchUpdatesTo(this)
}

Here, we pass both old and new list in RatingDiffCallback and then we calculate the difference.

After we know the difference, we update the ratings array list and notify the adapter via dispatchUpdatesTo .

This will update the old list with the new list. This is how you can update the list.

Why should we use DiffUtils?

1. Here is the performance chart which illustrates that using DiffUtil is better in a 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
Note: The maximum size of the list can be 2^26 because of the provided constraint.

This is how you can use DiffUtils to update the list in RecyclerView.

Happy Learning

Team MindOrks :)

Also, Let’s connect on Twitter, Linkedin, Github, and Facebook.