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.
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.
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.
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.
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
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With