Introducing ExpandableRecyclerView

Amanda Hill

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.Adapters 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 Genres with corresponding Artists.

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!