Introducing Expandable RecyclerView:
an open source library for expanding and collapsing groups using
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,
most of what
RecyclerView offers is an improvement over the existing
ListView, there are a few notable features missing from the
RecyclerView API. For example, we lost our dear friend
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
But as my dear (albeit fictional) friend Liz Lemon once said, “don’t be cry, it
okay” because today we are
ExpandableRecyclerView, an open source library with custom
RecyclerView.Adapters for expanding and collapsing groups to bring back some
of the former glory of
To understand this library is to understand
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
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
Above is music app showing a list of
Genres with corresponding
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
Classic while on the right it equals the
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
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
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
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.
See the full library and instructions for how to use it on Github.
Now! Go forth and play!