Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android DiffUtil.ItemCallback areContentsTheSame with different IDs

I'm using a RecyclerView to display a list of items that are retrieved from a database as Java objects. One of the fields in the object is the ID of the item in the database, so that further operations can be performed with it. My areContentsTheSame implementation compares various fields in the objects to determine if the contents of the item has changed.

The problem is that sometimes when the data source is refreshed the ID of the item changes without the visible contents changing (i.e. the same items are removed from the database and then added again, changing their ID in the process). In this case, my current areContentsTheSame implementation returns true. But when areContentsTheSame returns true, the RecyclerView views are not re-bound (onBindViewHolder is not called for the items for which areContentsTheSame returns true), so the views still hold a reference to the old object which contains the old ID thus causing an error when trying to do something with that item (such as the user tapping on it to display it).

I tried making areContentsTheSame return false when the ID changes, forcing the views to be re-bound even though no visible change has taken place, but this causes the "item changed" animation to show even though the item has not visibly changed. What I want is a way to either force the views to be re-bound without areContentsTheSame returning false or a way for areContentsTheSame to return true and trigger the re-binding without the animation being shown.

like image 482
Micheal Johnson Avatar asked May 22 '18 19:05

Micheal Johnson


People also ask

What is DiffUtil ItemCallback?

Callback for calculating the diff between two non-null items in a list. Callback serves two roles - list indexing, and item diffing. ItemCallback handles just the second of these, which allows separation of code that indexes into an array or List from the presentation-layer and content specific diffing code.

Are items the same Vs are contents the same?

areItemsTheSame(T, T) is called to see if two objects are the same. If not there may be a need to add/delete the item. areContentsTheSame is called only when the areItemsTheSame(T, T) return true. In this case, the item was available previously, but the content is changed, so the respective change should be displayed.

What is DiffUtil Android?

DiffUtil is a utility class developed to help with this, and Android RecyclerView using DiffUtil provides this feature. In this tutorial, you'll build a grocery list app. It uses DiffUtil to avoid redrawing all the cells in a list when only a subset of its data changes.


1 Answers

The best way to achieve what you want is to override the getChangePayload() method in your DiffUtil.Callback implementation.

When areItemsTheSame(int, int) returns true for two items and areContentsTheSame(int, int) returns false for them, DiffUtil calls this method to get a payload about the change.

For example, if you are using DiffUtil with RecyclerView, you can return the particular field that changed in the item and your ItemAnimator can use that information to run the correct animation.

Default implementation returns null.

So, make sure that your areContentsTheSame() method returns false when the database id changes, and then implement getChangePayload() to check for this case and return a non-null value. Maybe something like this:

@Override
public Object getChangePayload(int oldItemPosition, int newItemPosition) {
    if (onlyDatabaseIdChanged(oldItemPosition, newItemPosition)) {
        return Boolean.FALSE;
    } else {
        return null;
    }
}

It doesn't matter what object you return from this method, as long as it's not null in the case where you don't want to see the default change animation be played.

For more details on why this works, see the answer to this question: https://stackoverflow.com/a/47355363/8298909

like image 123
Ben P. Avatar answered Oct 22 '22 03:10

Ben P.