DiffUtil is a must!

First arrival of new data for your empty RecyclerView.Adapter is trivial, just consume the event and you are all set. I’ve spent a lot of time finding less hassle ways to trigger updates when new data arrives when my adapter is not empty. I had to look for ranges and indices and what not, many iterations for nothing.

As of 24.2.0, RecyclerView support library, v7 package offers really handy class called DiffUtil. It’s a life saver, it saves you from torments and hell of ranges and indices.

Code, pls.

DiffUtil needs to fetch most basic details about your two lists, like the size and basic item comparison. DiffUtil.Callback is used to provide DiffUtil with the basic information of the lists we are going to compare.

For my next trick I’m going to use simple Product model for my Adapter.

Yas! That’s the Callback

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);

}

DiffResult object that is returned from diff calculation, dispatches the changes to the Adapter, if any.

Payloads

Object returned in getChangePayload() is dispatched from DiffResult using notifyItemRangeChanged(position, count, payload), upon which is called Adapter’s onBindViewHolder(… List<Object> payloads) method.

@Override
public void onBindViewHolder(ProductViewHolder holder, int position, List<Object> payloads) {
if(payloads.isEmpty()) return;
else{
Bundle o = (Bundle) payloads.get(0);
for (String key : o.keySet()) {
if(key.equals(KEY_DISCOUNT)){
//TODO lets update blink discount textView :)
}else if(key.equals(KEY_PRICE)){
//TODO lets update and change price color for some time
}else if(key.equals(KEY_REVIEWS_COUNT)){
//TODO just update the review count textview
}
}
}
}

Huge lists

Documentation suggests that DiffUtil might take some time for large data sets and it is advised to move the calculation to background thread. Here is tiny RxJava example:

Dispatching the DiffResult to main thread:

@Override
public void onNext(DiffUtil.DiffResult result) {
result.dispatchUpdatesTo(mProductAdapter);
}

Conclusion:

DiffUtil calculates the difference between two given lists, the old list that is currently displayed and the new that you have recently obtained. The DiffResult contains the data updates that can be dispatched to your adapter.