ContentProvider Curses Cursor

This article is part 10 of 11 in the series Android Development

If you’ve developed Android apps, chances are you’ve written some Activity that displays a list of stuff. If you’ve done that, chances are, you’ve filled the list with data from a database cursor. If you’ve done that, chances are you created the Cursor by querying a content URI, which in turn invokes a ContentProvider.

So how do you handle errors with that?

To put that question into context, let’s look at some common things to check on a Cursor variable, and what they mean semantically:

  1. When calling a function that returns a Cursor (such as database query functions), the return value could be null. That signals an unspecified error on the part of the function.
  2. The Cursor object may have a row count of zero. Does that mean there was an error fetching data, or does it mean the data set is empty? Two different things.
  3. The function may throw an exception instead of returning a Cursor. That’s pretty cool, but Cursors are often used in conjunction with a ContentProvider, which may or may not run in the same process as the calling code. As yet Android is unable to propagate exceptions across processes.

In other words, as cool as it might be to use Cursors as the standard data interface for filling lists of stuff with, well, stuff, their interface really doesn’t lend itself to good error handling. And good error handling is at the root of stable software.

The Good

Make no mistake, Cursors are a good choice for what’s happening here, much better than iOS’s DataSource approach. For one thing, lists in Android aren’t filled directly via Cursors, but rather via an Adapter instance, and Android provides good Adapters for Cursors. In direct comparison, iOS only provides the equivalent of the Adapter interface.

Granted, the Adapter isn’t a Cursor, so that doesn’t really make for a fair comparison. So what’s great about Cursors?

  • If you read data from a database, they’re what you get anyway.
  • If you don’t read data from a database, there’s lightweight wrappers around arrays for providing the Cursor interface.
  • If the above isn’t enough, the Cursor interface is easy enough to implement — granted, you can just implement the Adapter interface, but sometimes one is better than the other.
  • Cursors are windowed. Strictly speaking, there’s a windowed Cursor implementation in Android that allows the Cursor adapters to fetch only relevant data out of a larger data set, namely data that’s currently being displayed to the user.
  • Cursors work cross-process on Android; more specifically, windowed cursors are behind the cross-process ContentProvider model. As a developer, you don’t see much of that, but it makes cross-process ContentProviders fast-ish.

All of which is great.

The Weird

There are a few, well, notable things happening with the Cursor API that you might not expect from such a database-y interface. All of that has to do with ContentProviders again.

  • There’s a mechanism for signalling to a remote process that a cursor returned from a ContentProvider has changed its data set.
  • There’s also an otherwise undocumented method for out-of-band communications with a Cursor; unfortunately, it’s not at all obvious how that might be useful.

Which makes it even sadder that something as simple as error propagation isn’t part of the Cursor API.

Solutions

So, to return back to the question, how do you deal with errors across this API?