Adapting to change, a RecyclerView redux
If your Android app has to display a scrolling list of elements, then you are most probably going to be using a RecyclerView (especially if it is based on a large set of data). The RecyclerView itself uses several components that work together to display your data efficiently so that your app will have super silky-smooth scrolling that your users will appreciate.
A LayoutManager, as the name suggests, decides how best to layout your data (e.g. in a simple list or grid, or maybe a staggered grid) and it provides the actual views to the RecyclerView to display.
The views themselves are represented by view holders which extend a ViewHolder object. Each view holder is responsible for displaying a single item of data from the list. The RecyclerView only creates as many view holders as are necessary to represent the data currently being displayed on the screen. As your users scrolls through your list, the RecyclerView takes any view holders no longer being displayed and re-uses them, rebinding them to the data that is scrolling onto the screen.
The RecyclerView delegates the management of the view holders to an Adapter which creates the view holders as needed. The adapter is also responsible for binding a view holder to the right data item in the list. The adapter needs to be told when the data items are changed (inserted, deleted or updated) so that it can communicate that efficiently to the RecyclerView.
What’s the difference?
The standard classes provide most of the common functionality needed; you’ll just need to create a view holder that can display an item of your data, and an adapter that creates and binds viewholders.
However, simple adapter implementations often aren’t very efficient. The adapter needs to be told when the data has changed and, because sometimes it isn’t a trivial job to work out what has changed, it was often easiest for a developer just to inform the adapter that the whole list had changed rather than the individual data items.
The support library in August 2016, version 24.2.0, introduced a new class DiffUtil that could efficiently calculate the differences between two collections of data. It could also dispatch a list of update operations to convert one list into another in a manner convenient for Adapters to communicate those update changes to the RecyclerView. Although a really cool util, it wasn’t immediately obvious how/when it should be used and developers
often always take the path of least resistance (a.k.a. the easiest route).
Welcome to the ListAdapter
And so recently the support library, version 27.1.0 (Feb 2018), provided a new tool for developers working with RecyclerViews, a new ListAdapter. (It’s found in the package android.support.v7.recyclerview.extensions or, as of Google I/O 2018, repackaged under androidx.recyclerview.widget and is not to be confused with the older ListAdapter class that’s been around since the beginning of Android and does something completely different!)
The new ListAdapter makes it easy for developers. It automatically uses the DiffUtil class for calculating list differences and it also performs the diff on a background thread, as it can sometimes be CPU intensive. Both these things help the RecyclerView animate content changes automatically without doing too much on the UI thread - great stuff. If you want to know more about how to use a ListAdapter, definitely take a gander at a Medium article by Craig Russell, he explains how to use it with Kotlin and LiveData: https://medium.com/@trionkidnapper/recyclerview-more-animations-with-less-code-using-support-library-listadapter-62e65126acdb
So, what’s the issue?
It is a common requirement to have an app design requiring a header view at the top of a list of scrolling data that scrolls with the list contents. When implementing this in an adapter, a fairly standard pattern is to pretend the header is part of the list of data, so that the adapter:
- Reports an extra item when asked how many items are in the list
- Returns the header item when asked for the first item in the list
- Applies an offset when notifying the RecyclerView which items have changed.
However, when working with a ListAdapter, this standard pattern doesn’t work. The ListAdapter handles the diff calculations and update notifications itself so that you can’t make it pretend there is a header row.
And the solution is… a ListAdapterWithHeader
My solution to this was to create an abstract class ListAdapterWithHeader that I could extend when creating my own adapters. ListAdapterWithHeader is based on the ListAdapter, but you can tell it how many header items there are before the list of data starts.
Here is an ExampleAdapter that extends and uses the ListAdapterWithHeader:
1. DiffUtil uses Eugene W. Myer’s difference algorithm to calculate the minimal number of updates to convert one list into another. Here's a link to his research paper if you’re interested http://xmailserver.org/diff2.pdf