Friday, June 29, 2018

Why and What is DiffUtil in android

Why DiffUtil / Problem with notifyDatasetChanged ?

When we use notifyDataSetChanged() method it updates all the view (all visible view on screen and few buffer view above and below the screen). It’s inappropriate way of updating list if just a few rows have changed. For example, your friend changes the status on WhatsApp then only that row should be updated instead of all the rows.


What is DiffUtil 

Android has provided a class called DiffUtil under version 7 support utils. As per android documentation, the definition of DiffUtil is as follow

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.


Performance 


The algorithm is optimized for space. It requires O(N) space to find out the number of operations for transforming the old list into the new one. It’s expected performance is O(N + D²). N is the sum of the lengths of the two lists and D is the length of the edit script. The edit script is the smallest set of deletions and insertions to transform the first list into the second.  


How to use?


DiffUtil.Callback is an abstract class and used as callback class by DiffUtil while calculating the difference between two lists. It has four abstract methods and one non-abstract method. You have to extend it and override all its methods-

Callback is an abstract class and needs to override methods about the sizes of the two lists and the comparison for items at particular index. I’m not familiar with the internal implementation, I had to add null checks (and left out some) just for sure. Of course, lists can be of any type, generifying the Callback will only deepens the comparisons of the items.

You may notice the getChangePayload() method, which is not abstract. This method is called when the areItemsTheSame() returns true, but areContentsTheSame() returns false, which means that we are talking about the same item but the fields data might have changed. Basically, this method returns the reason(s) why there is a difference in the lists.

In this, I used a Bundle to return more than one reason about the changes which make the compared items content not the same.

Once we have the callback, it is pretty easy.
@ Override
public void onNewProducts(List<Product> newProducts) {
    DiffUtil.DiffResult diffResult = DiffUtil.calculateDiff(new ProductListDiffCallback(mProducts, newProducts));
    diffResult.dispatchUpdatesTo(mProductAdapter);

}

getOldListSize()– Return the size of the old list.

getNewListSize()– Return the size of the new list.

areItemsTheSame(int oldItemPosition, int newItemPosition)– It decides whether two objects are representing same items or not.

areContentsTheSame(int oldItemPosition, int newItemPosition)– It decides whether two items have same data or not. This method is called by DiffUtil only if areItemsTheSame() returns true.

getChangePayload(int oldItemPosition, int newItemPosition)– If areItemTheSame() returns true and areContentsTheSame() returns false than DiffUtil utility calls this method to get a payload about the change.

DiffUtil also uses methods of RecyclerView.Adapter to notify the Adapter to update the data set:

notifyItemMoved()
notifyItemRangeChanged()
notifyItemRangeInserted()
notifyItemRangeRemoved()

You can read more details on RecyclerView.Adapter and its method here.



Important:


If the lists are large, this operation may take significant time so you are advised to run this on a background thread, get the DiffUtil#DiffResult then apply it on the RecyclerView on the main thread. Also max size of the list can be 2²⁶ due to implementation constraints.


Github Sample -> https://github.com/manishpathak99/DiffUtil-sample