Using the Loader pattern with SQLite on Android

In my Dutch Hangman game a random word is retrieved from a database containing about 100000 words. To provide a smooth user experience this needs to be done asynchronously. (Actually several asynchronous operations are performed during game initialization but I’ll restrict the discussion to the word loader here.) A CursorLoader would be an obvious choice but since it depends on a ContentProvider and I didn’t need one, I used the Loader extensions by Mark Murphy.

Since game initialization is quite delicate in this app and things could go wrong, I needed the ability to handle some possible execptions during the loading process. So I extended the SQLiteCursorLoader to this:

public class SQLiteCursorLoaderGraceful extends SQLiteCursorLoader {
 
	public Throwable error;
 
	public SQLiteCursorLoaderGraceful(Context context, SQLiteOpenHelper db,
			String rawQuery, String[] args) {
		super(context, db, rawQuery, args);
	}
 
	public void OnQueryException(RuntimeException throwable) {
		throw throwable;
	}
 
	@Override
	public Cursor loadInBackground() {
		try {
			return (super.loadInBackground());
		} catch (Throwable t) {
			error = t;
		}
		return (null);
	}
}

We need a LoaderManager instance to initialize the loader and to restart it when needed. For backward compatibility we use the one from the support library. Furthermore, we need to implement its callback interface. The loader is given a unique ID which is passed to the callback methods so we can switch between different loader actions (which does not apply to this single loader example). The database is retrieved from a DatabaseHelper instance.

public class GameActivity extends BaseGameActivity implements
		LoaderManager.LoaderCallbacks<Cursor> {
 
	private DatabaseHelper databaseHelper;
 
	// ...
 
	private boolean loaderInitialized;
	private static final int LOADER_ID = 1;
	private SQLiteCursorLoaderGraceful loader;
 
	// ...
}

The loader is initialized for its first query. For every next query the loader is restarted. A boolean is used her to track the loader’s state. (newGame() in this example is called elsewhere when the database is ready.) An indeterminate progress indicator is shown during asynchronous operations.

	protected void newGame() {
 
		// ...
 
		showProgress();
		if (!loaderInitialized) {
			getSupportLoaderManager().initLoader(LOADER_ID, null, this);
			loaderInitialized = true;
		} else {
			getSupportLoaderManager().restartLoader(LOADER_ID, null, this);
		}
	}

Create the loader in the onCreateLoader() override. The method gets the arguments passed from the initLoader() call above. We don’t use them here.

	@Override
	public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
		loader = new SQLiteCursorLoaderGraceful(this, databaseHelper,
				"select * from data order by random() limit 1", null);
		return loader;
	}

We’ll have the query results available in onLoadFinished(). Since our loader is graceful we can take some actions her if an error has occurred.

	@Override
	public void onLoadFinished(Loader<Cursor> arg0, Cursor cursor) {
		Throwable loaderError = ((SQLiteCursorLoaderGraceful) loader).error;
		if (loaderError != null) {
			Toast.makeText(this, loaderError.getMessage(), Toast.LENGTH_SHORT)
					.show();
			databaseHelper.recoverDatabase();
		} else {
			if (cursor.moveToFirst()) {
				word = cursor.getString(cursor.getColumnIndex("word"))
						.toUpperCase();
				// ...
				dismissProgress();
			}
		}
	}

Finally onLoaderReset() needs to be implemented. Resources can be freed here, if necessary. We’ll leave it empty. Do not close the cursor. The loader will handle this.

	@Override
	public void onLoaderReset(Loader<Cursor> arg0) {
		// TODO Auto-generated method stub
 
	}

Leave a Reply