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)SQLiteCursorLoader
(likeCursorLoader
, 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 aContentProvider
)
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.