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.
The best way to achieve what you want is to override the getChangePayload()
method in your DiffUtil.Callback
implementation.
When
areItemsTheSame(int, int)
returnstrue
for two items andareContentsTheSame(int, int)
returnsfalse
for them,DiffUtil
calls this method to get a payload about the change.For example, if you are using
DiffUtil
withRecyclerView
, you can return the particular field that changed in the item and yourItemAnimator
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