I use Cursors
extensively in my app, to load and occasionally write information from and to a database. I have seen that Honeycomb and the Compatibility Package
If anyone finds themselves in a similar situation, here's what I've done:
LoaderCallbacks
and handles all the queries you'll need.Context
and the Adapter
in question.UriMatcher
, might as well use the same ones)LoaderCallbacks
In my GlobalCallbacks
class:
public static final String PROJECTION = "projection";
public static final String SELECTION = "select";
public static final String SELECTARGS = "sargs";
public static final String SORT = "sort";
Context mContext;
SimpleCursorAdapter mAdapter;
public GlobalCallbacks(Context context, SimpleCursorAdapter adapter) {
mContext = context;
mAdapter = adapter;
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri contentUri = AbsProvider.customIntMatch(id);
if (contentUri != null) {
return new CursorLoader(mContext, contentUri, args.getStringArray(PROJECTION), args.getString(SELECTION),
args.getStringArray(SELECTARGS), args.getString(SORT));
} else return null;
}
@Override
public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
mAdapter.swapCursor(arg1);
}
@Override
public void onLoaderReset(Loader<Cursor> arg0) {
mAdapter.swapCursor(null);
}
And when I wanted to use a CursorLoader
(Helper.bundleArgs()
is the convenience bundling method):
scAdapt = new Adapters.NewIndexedAdapter(mHost, getMenuType(),
null, new String[] { "name" }, new int[] { android.R.id.text1 });
getLoaderManager().initLoader(
GlobalCallbacks.GROUP,
Helper.bundleArgs(new String[] { "_id", "name" }),
new GlobalCallbacks(mHost, scAdapt));
setListAdapter(scAdapt);
And in Helper:
public static Bundle bundleArgs(String[] projection, String selection, String[] selectionArgs) {
Bundle b = new Bundle();
b.putStringArray(GlobalCallbacks.PROJECTION, projection);
b.putString(GlobalCallbacks.SELECTION, selection);
b.putStringArray(GlobalCallbacks.SELECTARGS, selectionArgs);
return b;
}
Hope this helps someone else :)
EDIT
To explain more thoroughly:
Cursor
is initialised. We don't supply it with a Cursor
because GlobalCallbacks
will give the adapter the correct Cursor
in onLoadFinished(..)
LoaderManager
we want to initialise a new CursorLoader
. We supply a new GlobalCallbacks
instance (which implements Loader.Callbacks
) which will then monitor the loading of the cursor. We have to supply it with the adapter too, so it can swap in the new Cursor
once its done loading. At some point, the LoaderManager
(which is built into the OS) will call onCreateLoader(..)
of GlobalCallbacks
and start asynchronously loading dataHelper.bundleArgs(..)
just puts arguments for the query into a Bundle
(e.g. columns projection, sort order, WHERE clause)Fragment
's ListAdapter
. The cursor will still be null at this point, so it will show a loading sign or empty message until onLoadFinished()
is calledThere are two key benefits to using a CursorLoader
in your app over Activity.managedQuery()
:
AsyncTaskLoader
) so large data queries do not block the UI. This is something the docs recommended you do for yourself when using a plain Cursor
, but now it's done under the hood.CursorLoader
is auto-updating. In addition to performing the initial query, the CursorLoader
registers a ContentObserver
with the dataset you requested and calls forceLoad()
on itself when the data set changes. This results in you getting async callbacks anytime the data changes in order to update the view.Each Loader
instance is also handled through the singular LoaderManager
, so you still don't have to manage the cursor directly, and now the connection can persist even beyond a single Activity
. LoaderManager.initLoader()
and LoaderManager.restartLoader()
allow you to reconnect with an existing Loader
already set up for your query and, in some cases, instantly get the latest data if it is available.
Your Activity
or Fragment
will likely now implement the LoaderManager.Callback
interface. Calling initLoader()
will result in the onCreateLoader()
method where you will construct the query and a new CursorLoader
instance, if necessary. The onLoadFinished()
method will be fired each time new data is available, and will include the latest Cursor
for you to attach to the view or otherwise iterate through.
In addition, there is a pretty good example of all this fitting together on the LoaderManager
class documentation page:
http://developer.android.com/reference/android/app/LoaderManager.html
Hope that Helps!