Find further code and implementation here: https://github.com/browep/AndroidCursorLoaderTutorial
First, why use a Cursor Loader? Why is it better than the old way with the Activity.managedQuery call? The answer comes down to threading. The Cursor Loader class allows for the querying and filling of a Cursor do be done in a background thread. This frees up the UI thread to handle user interactions.
Side note: If you are attempting to do anything in Android beyond a simple “Hello, World” app then you need to read this article on threading, don’t worry it’s not that long.
This tutorial is going to give you two examples of how to use the CursorLoader, one super-simple barebones example and one that incorporates a ListView to display the contents. But first:
The ContentProvider Setup
Let’s start with the simplest example of a CursorLoader working with a ContentProvider. In the attached project there is a ContentProvider that will supply a list of dog breeds. And in order to emulate what a slow ContentProvider would look like it will Thread.sleep for 3 seconds.
public class SlowContentProvider extends ContentProvider {
public static final String TAG = SlowContentProvider.class.getSimpleName();
@Override
public boolean onCreate() {
Log.i(TAG,"onCreate");
return true;
}
@Override
public Cursor query(Uri uri, String[] strings, String s, String[] strings1, String s1) {
try {
Log.i(TAG,"sleeping");
Thread.sleep(3000);
MatrixCursor cursor = new MatrixCursor(new String[]{"_id","col1"});
for(String name : new String[]{"poodle","labrador","german shephard","boston terrier","hound"}){
cursor.addRow(new Object[]{0,name});
}
Log.i(TAG,"returning " + cursor);
return cursor;
} catch (InterruptedException e) {
Log.e(TAG,"error sleeping",e);
return null;
}
}
@Override
public String getType(Uri uri) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues contentValues) {
return null;
}
@Override
public int delete(Uri uri, String s, String[] strings) {
return 0;
}
@Override
public int update(Uri uri, ContentValues contentValues, String s, String[] strings) {
return 0;
}
}
This query method makes a MatrixCursor out of a static list after sleeping but it could be querying from a database, pulling from an API over the web, or pulling from a different ContentProvider. The beauty of the ContentProvider contract is that you don’t have to care about the “behind-the-scenes” stuff.
You also need to register this ContentProvider in your AndroidManifest.xml in order for it to be recognized, see https://github.com/browep/AndroidCursorLoaderTutorial/blob/master/AndroidManifest.xml#L24
A Very Simple Cursor Loader
Let’s see if we can get the CursorLoader created with the minimal of fuss. This class will create a CursorLoader that will put its results into a TextView.
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.text.Html;
import android.widget.TextView;
public class SimpleCursorLoader extends FragmentActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
public static final String TAG = SimpleCursorLoader.class.getSimpleName();
private static final int LOADER_ID = 0x01;
private TextView textView;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.simple_cursor_loader);
textView = (TextView) findViewById(R.id.text_view);
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
}
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
Uri.parse("content://com.github.browep.cursorloader.data")
, new String[]{"col1"}, null, null, null);
}
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
cursor.moveToFirst();
String text = (String) textView.getText();
while (!cursor.isAfterLast()) {
text += "<br />" + cursor.getString(1);
cursor.moveToNext();
}
textView.setText(Html.fromHtml(text) );
}
public void onLoaderReset(Loader<Cursor> cursorLoader) {
}
}
A Note about the CursorLoader
The CursorLoader library is not available in the core Android SDK before API level 11 ( Honeycomb ). Fortunately, the Android team made it available through the Compatibility package included in the SDK tools. See here for information on obtaining the support library. You’ll see that in the above class the import statements reference the android.support.v4.content package which is included in the support library jar instead of the android.content package which would have the same classes but only for API level 11 and above.
The above FragmentActivity (also in the support library ) implements the LoaderManager.LoaderCallbacks
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
Here we are initializing the Loader using the LoaderMangager, passing it a an id ( can be any int that you want), a Bundle ( which will be passed to the LoaderManager.LoaderCallbacks.onCreateLoader method), and then we are identifying this Activity as the class that implements the callbacks.
Below that we are implementing the LoaderCallbacks. The important one for this example is the onCreateLoader method. In it we need to return a Loader that will describe the query we are trying to run. It needs the URI for the content provider as well as the arguments to construct the query: projection, selection, selection args, sort order.
When it’s time to load the cursor, the onCreateLoader method will be called. The CursorLoader that is returned will be used to construct the query, interact with the ContentProvider on a background thread and then return the filled cursor to onLoadFinished
In the above example we see the onLoadFinished just fills up a TextView with the cursor content. Let’s try a more interesting example.
Integrating a CursorLoader and a ListView
Although the above example is nice, it’s not very interesting. Let’s use it with something that’s more realistic like a ListView. The below Activity is very similar to the previous Activity but we have added a ListView to be populated with the Cursor contents.
public class ListViewCursorLoader extends FragmentActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final int LOADER_ID = 0x02;
private CursorAdapter adapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.listview_cursor_adapter);
getSupportLoaderManager().initLoader(LOADER_ID, null, this);
adapter = new SimpleCursorAdapter(this, R.layout.listview_item_layout, null,
new String[]{"col1"}, new int[]{R.id.text_view},
Adapter.NO_SELECTION);
ListView listView = (ListView) findViewById(R.id.list);
listView.setAdapter(adapter);
}
public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
return new CursorLoader(this,
Uri.parse("content://com.github.browep.cursorloader.data")
, new String[]{"col1"}, null, null, null);
}
public void onLoadFinished(Loader<Cursor> cursorLoader, Cursor cursor) {
adapter.swapCursor(cursor);
}
public void onLoaderReset(Loader<Cursor> cursorLoader) {
adapter.swapCursor(null);
}
}
Here again we are init’ing the Loader in the onCreate method just as we did in the previous example. We are also creating a CursorAdapter to translate the Cursor content to a ListView. The SimpleCursorAdapter makes it simple to map the Cursor content to a View value. It does it by taking as arguments:
- A layout file to inflate
- A Cursor with the contents, which we are leaving as null for now.
- A list of columns who’s values we are going to grab
- A list of View id’s in the inflated layout to apply said column values Our example is simple enough that we just have one column to apply to one TextView in the inflated layout.
The final argument in the SimpleCursorAdapter constructor is a flag for behavior. This example has Adapter.NO_SELECTION. This mean the Cursor will NOT re-query if the content is changed. You do not want CursorAdapter.FLAG_AUTO_REQUERY as this will make the Cursor requery using the UI thread which is what we avoiding by using a CursorLoader in the first place. The CursorLoader will handle any data changes and then call onLoadFinished so that everything happens in a background thread.
Our onCreateLoader method stays the same but our onLoadFinished is a little different. Since we are being given a Cursor we can give it to the Adapter we created and it should handle the rest.
To round it out we implement onLoaderReset which will be called if the Loader’s data is reset/unavailable/invalid.
So there you go, working with CursorLoader is easy and you will see the advantages in the more responsive UI. All code for this tutorial including needed jars and a full Android project can be found in the Github repo at the top of the tutorial.