Introducing Expandable RecyclerView:
an open source library for expanding and collapsing groups using RecyclerView.Adapter
.
The Why
At Google IO 2014, Google introduced the RecyclerView
widget - an entirely
updated approach to showing a collection of data. RecyclerView
was designed
to be more flexible and extendable than its predecessor, ListView
. While
most of what RecyclerView
offers is an improvement over the existing
functionality of ListView
, there are a few notable features missing from the
RecyclerView
API. For example, we lost our dear friend OnItemClickListener
and our lesser, but still kinda close friend, ChoiceModes
. And if all that
lost functionality wasn’t depressing enough, we also lost all of the
subclasses and custom implementations of ListView
, like
ExpandableListView
.
But as my dear (albeit fictional) friend Liz Lemon once said, “don’t be cry, it
okay” because today we are
introducing ExpandableRecyclerView
, an open source library with custom
RecyclerView.Adapter
s for expanding and collapsing groups to bring back some
of the former glory of ExpandableListView
.
The What
To understand this library is to understand RecyclerView.Adapter
. According
to the docs:
[RecyclerView] Adapters provide a binding from an app-specific data set to views that are displayed within a RecyclerView
In other words, the adapter is just the middleman: translating indexes from
some backing data structure, to views to be passed along to a RecyclerView
to be seen on screen. When you are showing a single dimensional list of
objects, this translation is simple. The indexes of the objects in the backing
data set directly correspond to the indexes of views displayed on screen. But
when you want to display two dimensional data, like in case of an expandable
list, this translation gets a bit more tricky. When the indexes of the views
are the same as those of the backing data set, there is only a single truth to
maintain. But when those indexes become disparate, so does the source of
truth, leaving you with two possible truths.
Confused? Me too. Let’s look at a sample app, for some clarity and see how the
ExpandableRecyclerViewAdapter
works.
The How
Above is music app showing a list of Genre
s with corresponding Artist
s.
The numbers to the left of the phones represent the view indexes. As you can
see both phones contain 9 row items. But if you look at row 6, you will see
the data on each phone is different. On the left, view index 6 equals the
Genre
object, Classic
while on the right it equals the Artist
object,
Miles Davis
. Enter the double source of truth problem. The view indexes
(truth numero uno) are different from the backing data structure indexes
(truth numero dos).
If you thought this double source of truth problem was confusing, don’t worry,
you’re not alone. The RecyclerView.Adapter
also can’t understand it. In
fact, the adapter isn’t even designed to handle two dimensional data. If you
look at all the callbacks and methods on RecyclerView.Adapter
you’ll notice
it just passes around a single int
called position
.
So to make this whole thing work, all we need some way to translate the two dimensional data to a flattened, single dimensional data set for the adapter to use.
The Okay, But Really How
Enter ExpandableList
and ExpandableListPosition
.
ExpandableList
acts as a translator between the flat list position (i.e.
what groups and children you see on the screen) to and from the full backing
list of groups and their children - the unflattened position.
ExpandableListPosition
is an object that can refer to either a group’s
position or a child’s position. Referring to a child’s position requires both
a group position (the group containing the child) and a child position (the
child’s position within that group). The advantage of containing all the
information about an expandable list position item in one class is that it
saves us from having to iterate over the backing data set multiple times to
determine whether an expandable list position was a child or a group and, in
the case of a child, what that child’s position is within its containing
group.
And we can thank Google for thinking of something so clever, as
ExpandableListPosition
is actually from the Android SDK. The only reason we
even had to include it as a separate class in this library was because it has
package local scope within the Android SDK.
The Where
compile 'com.thoughtbot:expandablerecyclerview:1.0'
See the full library and instructions for how to use it on Github.
The When
Now! Go forth and play!