Using DiffUtil in Android RecyclerView
- 5 minsThe notifyDataSetChanged() method is inefficient
To tell RecyclerView that an item in the list has changed and needs to be updated, the current code calls notifyDataSetChanged() in the SleepNightAdapter, as shown below.
var data = listOf<SleepNight>()
set(value) {
field = value
notifyDataSetChanged()
}
However, notifyDataSetChanged() tells RecyclerView that the entire list is potentially invalid. As a result, RecyclerView rebinds and redraws every item in the list, including items that are not visible on screen. This is a lot of unnecessary work. For large or complex lists, this process could take long enough that the display flickers or stutters as the user scrolls through the list.
To fix this problem, you can tell RecyclerView exactly what has changed. RecyclerView can then update only the views that changed on screen.
RecyclerView has a rich API for updating a single element. You could use notifyItemChanged() to tell RecyclerView that an item has changed, and you could use similar functions for items that are added, removed, or moved. You could do it all manually, but that task would be non-trivial and might involve quite a bit of code.
Fortunately, there’s a better way.
DiffUtil is efficient and does the hard work for you
RecyclerView has a class called DiffUtil which is for calculating the differences between two lists. DiffUtil takes an old list and a new list and figures out what’s different. It finds items that were added, removed, or changed. Then it uses an algorithm called a Eugene W. Myers’s difference algorithm to figure out the minimum number of changes to make from the old list to produce the new list.
Once DiffUtil figures out what has changed, RecyclerView can use that information to update only the items that were changed, added, removed, or moved, which is much more efficient than redoing the entire list.
Refresh list content with DiffUtil
In order to use the functionality of the DiffUtil class, extend DiffUtil.ItemCallback.
- Put the cursor in the
SleepNightDiffCallbackclass name. - Press
Alt+Enter(Option+Enteron Mac) and select Implement Members. - In the dialog that opens, shift-left-click to select the
areItemsTheSame()andareContentsTheSame()methods, then click OK.
- Inside
areItemsTheSame(), replace theTODOwith code that tests whether the two passed-inSleepNightitems,oldItemandnewItem, are the same. If the items have the samenightId, they are the same item, so returntrue. Otherwise, returnfalse.DiffUtiluses this test to help discover if an item was added, removed, or moved
override fun areItemsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem.nightId == newItem.nightId
}
- Inside
areContentsTheSame(), check whetheroldItemandnewItemcontain the same data; that is, whether they are equal. This equality check will check all the fields, becauseSleepNightis a data class.Dataclasses automatically defineequalsand a few other methods for you. If there are differences betweenoldItemandnewItem, this code tellsDiffUtilthat the item has been updated.
override fun areContentsTheSame(oldItem: SleepNight, newItem: SleepNight): Boolean {
return oldItem == newItem
}
Use ListAdapter to manage your list
It’s a common pattern to use a RecyclerView to display a list that changes. RecyclerView provides an adapter class, ListAdapter, that helps you build a RecyclerView adapter that’s backed by a list.
ListAdapter keeps track of the list for you and notifies the adapter when the list is updated.
- Change the class signature of your Adapter to extend
ListAdapter. - If prompted, import
androidx.recyclerview.widget.ListAdapter. - Add
SleepNightas the first argument to theListAdapter, beforeSleepNightAdapter.ViewHolder. - Add
SleepNightDiffCallback()as a parameter to the constructor. TheListAdapterwill use this to figure out what changed in the list. Your finishedSleepNightAdapterclass signature should look as shown below
class SleepNightAdapter : ListAdapter<SleepNight, SleepNightAdapter.ViewHolder>(SleepNightDiffCallback()) {
keep the list updated
Your code needs to tell the ListAdapter when a changed list is available. ListAdapter provides a method called submitList() to tell ListAdapter that a new version of the list is available. When this method is called, the ListAdapter diffs the new list against the old one and detects items that were added, removed, moved, or changed. Then the ListAdapter updates the items shown by RecyclerView.
In your fragment tell the adapter when your data has been updated:
sleepTrackerViewModel.nights.observe(viewLifecycleOwner, Observer {
it?.let {
adapter.submitList(it)
}
})
Now run your app. It runs faster, maybe not noticeably if your list is small.