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 ofgetCount()
of theCursorAdapter
and the number of headings, determined from the number of breaks.getViewTypeCount()
, presumably returning2
, or perhapsgetViewTypeCount()
of theCursorAdapter
plus one for the headings.getItemViewType()
, which would use a privateiCanHazHeading()
method to determine if theposition
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 useiCanHazHeading()
to determine if theposition
is a heading, and return the headingView
if so. The headingView
would be populated based on the category column in theCursor
. Otherwise, for regular rows, it would delegate to the wrapped adapter, modifyingposition
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.