What are the benefits of CursorLoaders?
Solution 1
There are two key benefits to using a CursorLoader
in your app over Activity.managedQuery()
:
- The query is handled on a background thread for you (courtesy of being build on
AsyncTaskLoader
) so large data queries do not block the UI. This is something the docs recommended you do for yourself when using a plainCursor
, but now it's done under the hood. -
CursorLoader
is auto-updating. In addition to performing the initial query, theCursorLoader
registers aContentObserver
with the dataset you requested and callsforceLoad()
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
Solution 2
If anyone finds themselves in a similar situation, here's what I've done:
- Created a class which implements
LoaderCallbacks
and handles all the queries you'll need. - Supply this with a
Context
and theAdapter
in question. - Create unique IDs for each query you'll use (if you use a
UriMatcher
, might as well use the same ones) - Make a convenience method which transfers queries into the bundle required for the
LoaderCallbacks
- That's pretty much it :) I put some of my code below to show exactly what I did
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:
- First, an adapter with a null
Cursor
is initialised. We don't supply it with aCursor
becauseGlobalCallbacks
will give the adapter the correctCursor
inonLoadFinished(..)
- Next, we tell
LoaderManager
we want to initialise a newCursorLoader
. We supply a newGlobalCallbacks
instance (which implementsLoader.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 newCursor
once its done loading. At some point, theLoaderManager
(which is built into the OS) will callonCreateLoader(..)
ofGlobalCallbacks
and start asynchronously loading data -
Helper.bundleArgs(..)
just puts arguments for the query into aBundle
(e.g. columns projection, sort order, WHERE clause) - Then we set the
Fragment
'sListAdapter
. The cursor will still be null at this point, so it will show a loading sign or empty message untilonLoadFinished()
is called
Related videos on Youtube
Comments
-
Alex Curran almost 2 years
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 have newLoader
classes designed to help with loading data in a "good" way.Essentially, are these new classes (in particular
CursorLoader
) considerably better than previous methods of managing data? What is the benefit of aCursorLoader
over managedCursors
for example?And I use a
ContentProvider
to deal with data, which obviously takesUris
but how does this mesh with theinitLoader()
method? Must I set up each of myFragments
to use Loaders individually? And how unique does the id need to be for each loader, is it over the scope of my app or just a fragment? Is there any simple way of simply passing aUri
to a CursorLoader to query my data?All I can see at the moment is that Loaders add an unnecessary extra step to getting my data into my app, so can someone explain them to me better?
-
Alex Curran over 12 yearsCool, thanks for that explaination! Just a couple of questions: is the id supplied to
initLoader()
mean to be unique or each set of data you need to query? And because I have multiple activities which each use different tables of my database, is there any way I can create a class which handles eachCursorLoader
, much like aContentProvider
can handle multipleCursors
withquery
? -
devunwired over 12 yearsThe id value identifies a unique
Loader
instance with the manager, you can get access to the same one again if you askLoaderManager
sometime after it's already been created. Since eachCursorLoader
is basically created to execute a specific query, you could say that an id maps to a data set. Although there are methods onCursorLoader
to dynamically change the query parameters as well, so that's not required to be the case. -
devunwired over 12 years
LoaderManager
is really the single class that handles eachLoader
for you, but if you mean can you create a single class to manageLoaderManager.Callback
for multipleLoader
instances, the answer is yes. Multiple callbacks can be attached for a singleLoader
id value, and multiple ids could point back to the same callback implementation. -
Andy almost 12 yearsHey, I have a quick question. First though, I want to thank you for providing this. You really helped me understand what I needed to do and where to look. So my question is about your convenience method. I'm not really sure how it works. Could you explain that last bit of code if you have time please. Thanks again!
-
Alex Curran almost 12 yearsSee my updated answer, let me know if you need anything further
-
Andy almost 12 yearsOhhhhhh, I see. I finally got it!! I kind of understand. But How is that
Bundle
sent intoonCreateLoader
? The tutorial I saw does it right in onCreateLoader, never really using it. I was under the assumption that I didn't need to use it. -
Alex Curran almost 12 yearsI think the Bundle is more useful if you want to dynamically change your query. For example if you had a list of items and you wanted all of them, you wouldn't need the Bundle, but it could come in handy if you want to then filter them, as you wouldn't need two different CursorLoaders (you'd just check if there was a filter query in the Bundle).
-
Andy almost 12 yearsAhh, I really appreciate your time. One more thing, could you rewrite that convenience method exactly how you have it, and which class its in? But besides that, i'm all set. You have helped me out immensely. I can't thank you enough!
-
Alex Curran almost 12 yearsI'm afraid I don't have it any more (I was having issues with ProGuard and this class so I got rid of it), but I'll rewrite it above
-
Andy almost 12 yearsOh, ok. I really appreciate it.
-
Alex Curran almost 12 yearsNo probs, I've rewritten the base bundleArgs() above, you can just make the others passing null arguments.