Adding Headings to a CursorAdapter

A recent StackOverflow question re-raised an oft-cited challenge: adding headings to a CursorAdapter. My examples of headings in lists use MergeAdapter, which is suitable for where each individual section can be represented by its own ListAdapter (e.g., a CursorAdapter on a distinct Cursor). In some cases, that’s not a great approach — doing a single query and having a single Cursor would be superior. However, then introducing the headings becomes a bit more of a challenge.

Eventually, I’ll probably run into a case where I have this itch and will scratch it. At that point, I’ll poke at the Android Open Source Project for places where it uses this technique (e.g., contacts) and see how they approach it. Then, after failing to understand most of the code I read, I’ll cook up something.

Since I don’t really have that itch to scratch right now, but since this issue comes up, here’s my shoot-from-the-hip way I’d approach the problem:

As the “for instance” data model, let’s imagine a database of books and categories. Each book is in one category, to keep this story simple. We want a ListView of all books, sorted by category, with headings for the categories.

Our query would use a join to bring back the category title, along with the relevant book data (e.g., book title). We would also do an ORDER BY to sort our data by category, then by whatever the order is to be within each heading (e.g., by book title).

After doing the query and getting the Cursor, I would spin through the Cursor and find out the “breaks” when we switch from one category to the next. You could do this with a separate query, and that might be faster, though you would have to feel comfortable that your data would not change between those two queries. I’d have to research SQLite’s transaction semantics to determine if a transaction will perform enough locking to cover this case.

Given an ArrayList of break information, I would create an AdapterWrapper or WrapperListAdapter that would be my decorating adapter supplying the headings, using a regular CursorAdapter on my Cursor for the books. Let’s call this decorating adapter BookCategoryAdapter for the sake of this post.

BookCategoryAdapter would simply delegate most ListAdapter behaviors to the underlying CursorAdapter. Notable overrides would include:

  • getCount() would return the sum of getCount() of the CursorAdapter and the number of headings, determined from the number of breaks.
  • getViewTypeCount(), presumably returning 2, or perhaps getViewTypeCount() of the CursorAdapter plus one for the headings.
  • getItemViewType(), which would use a private iCanHazHeading() method to determine if the position represents a heading or not. If it does, it would return the view type for the heading; if not, it would return a view type for the book. iCanHazHeading() would make its determination based on the breaks.
  • getView() would also use iCanHazHeading() to determine if the position is a heading, and return the heading View if so. The heading View would be populated based on the category column in the Cursor. Otherwise, for regular rows, it would delegate to the wrapped adapter, modifying position to take into account any headings that appear before this row.

The StackOverflow question in, er, question indicated that performance was an issue. The primary point of overhead in this implentation is determining where the category breaks are. However, that should really only be slow for massive lists, and the solution to that is simple: don’t use massive lists. I know there are some developers who think that 10,000-item ListViews are awesome. I find them atrocious, whether in a desktop, Web, or mobile application. If your data is potentially unbounded like this, you need a better UI model that lets the user deal with a smaller list (e.g., list of categories, each leading to a list of books in that category). Expecting the user to sift through 10,000 entries on a 3” smartphone screen is bad, and adding headings will not help much.

Want an expert opinion on your Android app architecture decisions? Perhaps Mark Murphy can help!