Test-Driven Android: Testing Multiple Cursor Loaders for One Activity/Fragment

Reading Time: 3 minutes

I’ve found some favorite resources on writing Android apps. One of my favorites is Virgil Dobjanschi’s Google I/O Talk on Android apps as REST clients. This talk is relevant to every business-related app I’ve ever built, because most apps, on a general level, solve a lot of the same problems. Fetching data from an API and displaying it on the app is one of those problems. How many apps that I’ve built have demanded this functionality? All of them.

Virgil describes a couple of different levels of advancement for fetching and displaying data in an Android app. Lately, I have favored the most advanced one: making a call with a service, sticking the results in a database, and accessing them with a content provider and cursor loader to display on the UI.

Lots of tutorials will help you implement this pattern…on the simplest level. But what happens when, say, you have multiple tables, and you want to register updates for all of them on the same UI? If your LoaderCallbacks implementer is pulling from multiple databases, then your onLoadFinished() method has to distinguish between different CursorLoaders for each of your databases. And if the generic type for any of the CursorLoaders is the same, then you need to distinguish between them from within one onLoadFinished() method—because you can’t implement that method twice with the same signature.

Everyone on StackOverflow describes one of two solutions.

1) Implement two separate onLoadFinished methods and change one of the Loader’s generic types to not-a-cursor for the express purpose of loading them both in one activity/fragment. This works by adding a layer of indirection that can make the code confusing for developers who are unfamiliar with the pattern.

2) rely on the loader ID, which makes total sense, and works for the implementation code. The problem comes, as it frequently does with mobile development, when you try to unit test this approach. When we call onCreateLoader() from a test, the loader id does not get set. 

Seriously, take this code:


public class NutritionFactFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int FRUIT_LOADER = 1;
public static final int VEGGIE_LOADER = 2;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case FRUIT_LOADER:
CursorLoader fruitCursorLoader = new CursorLoader(
getActivity(),
Uri.parse(NutritionContract.Fruit.CONTENT_URI),
null,
null,
null,
null
);
return fruitCursorLoader;
case VEGGIE_LOADER:
CursorLoader veggieCursorLoader = new CursorLoader(
getActivity(),
Uri.parse(NutritionContract.Veggies.CONTENT_URI),
null,
null,
null,
null
);
return veggieCursorLoader;
default:
return null;
}
}
}

Print the loader.getId() for the loader that gets returned when you call onCreateLoader() in your tests. It will be zero in both cases, despite the fact that we expect the ids to be 1 and 2.

So what to do? I spelunked through the CursorLoader code a little and found a method that sets the id on the CursorLoader: registerListener(int id, OnLoadCompleteListener<Cursor> listener).

So when we add this call to our onCreateLoader() method, we get this:


public class NutritionFactFragment extends Fragment implements LoaderManager.LoaderCallbacks<Cursor> {
public static final int FRUIT_LOADER = 1;
public static final int VEGGIE_LOADER = 2;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
switch (id) {
case FRUIT_LOADER:
CursorLoader fruitCursorLoader = new CursorLoader(
getActivity(),
Uri.parse(NutritionContract.Fruit.CONTENT_URI),
null,
null,
null,
null
);
fruitCursorLoader.registerListener(FRUIT_LOADER, null);
return fruitCursorLoader;
case VEGGIE_LOADER:
CursorLoader veggieCursorLoader = new CursorLoader(
getActivity(),
Uri.parse(NutritionContract.Veggies.CONTENT_URI),
null,
null,
null,
null
);
veggieCursorLoader.registerListener(VEGGIE_LOADER, null);
return veggieCursorLoader;
default:
return null;
}
}
}

and that sets the loader id!

So now you can have a clean switch case in your onLoadFinished() method:


@Override
public void onLoadFinished(Loader<Cursor> loader, final Cursor data) {
switch (loader.getId()) {
case FRUIT_LOADER:
//do things with the fruits data
break;
case VEGGIE_LOADER:
//do things with the vegetables data
break;
}
}

If you have been banging your head against this (as I did at first), please pay it forward and share this post with another Android dev who might like to know.

cursor loader

 

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.