CWAC-LoaderEx and Failed Abstractions

When the Loader framework showed up in Android 3.0, my initial interest was tempered by the fact that there was only one concrete Loader implementation provided by Android. That was CursorLoader , and it required a ContentProvider as the backing store for the data. After the umpteenth question on StackOverflow about trying to use CursorLoader without a ContentProvider , I elected to take a shot at providing other concrete Loader implementations. Specifically, I created the CWAC-LoaderEx library, with three such Loader classes:

SharedPreferencesLoader (because initially loading preferences involves disk I/O)

(because initially loading preferences involves disk I/O) SQLiteCursorLoader (like CursorLoader , but for working directly with SQLite)

(like , but for working directly with SQLite) SQLCipherCursorLoader (same as above, but for SQLCipher for Android)

All of them sucked, and so I never used them. Since I never used them, I never maintained them. And, since I never maintained them, I have officially discontinued the project.

This is not to say that CursorLoader sucks. CursorLoader is a fine solution for three common problems:

How do we retrieve data in the background? How do we retain that data across configuration changes? How do we arrange to get updated data when the data changes elsewhere in the app?

The first two are handled mostly by the framework itself, through classes like LoaderManager and AsyncTaskLoader . Anyone else implementing an AsyncTaskLoader will get those two features “for free”, more or less.

The third one is the sticking point and is why my loaders sucked.

The framework, on its own, has little means of determining when some arbitrary data changes. Hence, the implementation of this mostly is up to the Loader , with minimal framework assistance.

The problem is that the interesting cases for this feature is where the data is changed in some random spot in your app. For example, a service might update a database. Or some other activity (like a PreferenceActivity ) might update a preference.

This begs the question: how does a Loader , attached to a Activity , find out when data of interest changes anywhere in the app?

In the case of SharedPreferences , there is a listener. However, the SharedPreferences instance is not replaced when it is updated – rather, it is updated in situ. This runs afoul of an optimization inside of the Loader framework, where it is assumed that data changes are reflected in object instance changes, such as where a new Cursor replaces an old Cursor . Hence, my SharedPreferencesLoader never really provided a new SharedPreferences when another part of the app updated those preferences.

In the case of a database, we do not even have an Android-supplied listener. Instead, our code that updates the database would have to somehow let the Loader know about those updates. For simplistic cases, such as the Activity hosting the Loader doing its own database manipulation, this is easy. For cases where the data-updating component has no access to the Loader , this is difficult.

The Loader framework effectively assumes that all implementations of loaders:

Have singleton manager objects (like a ContentProvider )

) Have distinct data objects per update (like a Cursor from a ContentProvider )

Anything not meeting those requirements does not fit.

Ideally, when the Loader framework was introduced, three or more concrete implementations would have gone into the Android SDK. The act of creating those implementations would have put stress on the Loader design, probably highlighting the aforementioned implicit requirements. That, in turn, might have changed the design. As it stands, I consider Loader to be a bit of a failed abstraction – it works very well for the one concrete implementation and is rather awkward for everything else that I have seen or tried.

So, feel free to use CursorLoader , if you wish to load data from a ContentProvider , whether that is one you wrote or one provided by somebody else (e.g., ContactsContract ). And, if you happen to have an environment where a custom Loader really can fulfill all the requirements, explicit and implicit, feel free to do so.

In my case, if I am going to have some singleton manager object, with distinct data objects per operation, I am going to use something more flexible than Loader , such as an event bus.

Interested in learning Kotlin? Check out the Klassbook for Kotlin language lessons that you can run right in your browser!

— Mar 31, 2014