Android Loaders, first introduced in Honeycomb (API 11), enable app devs to use a more robust pattern for handling data loading. Data loading is (generally) easy, so why was this needed? To answer that, we first have to take a step back and take a glimpse at an app’s internals.

 

Android App Context of Execution

It’s very common in Android app development to see references to the “UI thread”.  This is the main thread of an application and how the framework notifies the app of events.  Take an Activity for example: its lifecycle callbacks as well as UI event callbacks (onTouch(), onClick(), etc.) are all executed on the main thread.  Why is this relevant to a data discussion? Two reasons:

  1. While executing a lifecycle or UI callback, all other similar events for your app are waiting to be processed
  2. The current state of your Activity (or Fragment can change and has to be coordinated with background threads

Let’s start with the first item. If the Activity is in the running state (e.g. onResume() has run) and a click listener’s onClick() is called, any additional touch, click or lifecycle changes are queued for processing after the listener returns. If the onClick() takes a long time to complete, the other events are sitting in the queue waiting to be processed. If we have even a moderately sized app there’s a chance we also have a BroadcastReceiver or Service which could have events pending as well. These components are also called on the main thread of our app’s process. It should be pretty obvious how long running operations on the main thread can cause problems. Even small data loads, such as from an internal SQLite database, can take time due to I/O or processing. So, the bottom line is our data loading really needs to be handled off the main thread.

But, now that we have introduced a secondary thread (or more!) to the mix, we have to worry about lifecycle of the owning component. What happens if our thread completes its work but the Activity is no longer in the running state? There’s potentially a big problem here. Therefore, resolving the first item above leads us into the second item. Funny how that works out!

 

Introducing the Forklift: the Android Loader

Anyone who has been working with Android prior to API 11 can tell you war stories around managing background threads and maintaining state across configuration changes (e.g. screen rotation). While these things are not impossible to deal with, they do require some careful considerations which are prone to problems. Along with the introduction of Fragments in API 11, the Android Loader APIs were also added to the framework. This helps make it easier to solve the problem of background data handling and coordinating with lifecycle changes.  How does it do this? The Loader mechanism is integrated with the Activity and Fragment classes of the framework.  For those targeting pre-Honeycomb devices: don’t fear, you can still leverage the Android Loader mechanism (and Fragments!) by way of the compatibility library.  More on that later.

Back to how it works. Within the Activity and Fragment classes there is a LoaderManager which maintains the instance of each Loader and is aware of the lifecycle of the owning component. The manager is used to create, destroy and restart the Loader when requested by the app code.  Once the Loader has been created, the manager will control the Loader execution based on state.  The manager also handles providing the results to the app when the Loader has returned data.  The Loader itself is responsible for managing the data it retrieves and where/how it is pulled.  Loaders are intended to be asynchronous in nature, doing the loading/work on another thread, but the coordination here isn’t exactly clear from the documentation.  This diagram shows a high level view of what typical Loader operation looks like.

Android Loader High Level Operation

 

 

As you can see, when the Loader is initialized via the call to LoaderManager.initLoader(), the Loader is created and once the Activity or Fragment goes through its ‘starting’ state the Loader is told to start loading.  It is at this point the Loader can start its background loading.  Think of it like a forklift (and driver) which has been given instructions on what/where to load, goes off to fetch the load, and will come banging on its supervisor’s door when it is ready to drop it off.

The Loader class itself is abstract, it is up to the implementation to manage the data loading. There is a concrete implementation which can be used for databases or ContentProviders called CursorLoader. This class is derived from AsyncTaskLoader, which is an abstract class providing most of the basic plumbing for background operations. As you may have already guessed from the name, it uses AsyncTask for its background thread handling. For those wishing to create their own Loader classes, the AsyncTaskLoader is what you will most likely extend for your implementation. I’m not going to cover custom Loader implementations in this article, that’s a deeper topic for another day. Once the background loading has been completed, the Loader provides the results via its deliverResult() method. This method is always called on the main thread of the app, so it is up to the Loader implementation to call this method in the correct context of execution.

 

Using a Loader

Now that we have covered the background concepts of the Loader, and even some of its implementation details, let’s actually use one to get a better feel for it.  The simple example I’m going to walk through is an app which shows the call log for the incoming calls on a phone. Something I’ve not discussed yet is how the LoaderManager actually creates the Loader instance after initLoader() is called.  It is up to our app code to do this, and it is given an opportunity to do so at the correct time by registering callbacks with the LoaderManager during this init call.  The callback mechanism is how our app code is informed which Loader is desired as well as when it is done loading and if it needs to be restarted.  Here in our Activity‘s onCreate() method, after inflating our layout and creating an adapter for our ListView, we init an instance of the loader, specified by an ID (which is meaningful for our app):

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mInCallList = (ListView)findViewById(R.id.list_in_calls);

        mAdapter = new SimpleCursorAdapter(this,
                                           R.layout.list_recent_in_call,
                                           null,
                                           IN_CALL_COLUMNS,
                                           IN_CALL_RES_IDS,
                                           0);
        mAdapter.setViewBinder(MY_VIEW_BINDER);
        mInCallList.setAdapter(mAdapter);

        Log.d(TAG, "onResume: initializing loader...");
        getLoaderManager().initLoader(LOADER_MY_ID, null, this);
    }

Notice the last argument of the initLoader() call, this. Our Activity is implementing the LoaderManager.LoaderCallbacks interface:

public class MainActivity extends Activity implements LoaderManager.LoaderCallbacks {

Our implementation is shown below. The onCreateLoader() method is where we create a Loader which makes sense for our application. We are going to pull call logs from the CallLog provider, so we’ll use a CursorLoader, as shown in the code.  The CursorLoader returns a Cursor object holding any data it pulled from the provider which matched the projection and selection arguments.  In our case, we did not specify a projection, so we get all columns. We did, however, specify a selection: we only want “incoming” call types.

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        Loader                  ret = null;
        String                  sel;
        String[]                selArgs;

        Log.d(TAG, "onCreateLoader for id: " + Integer.toString(id));

        switch (id) {
            case LOADER_MY_ID:
                sel = CallLog.Calls.TYPE + " = ?";
                selArgs = new String[] { Integer.toString(CallLog.Calls.INCOMING_TYPE) };
                ret = new CursorLoader(this,
                                       CallLog.Calls.CONTENT_URI,
                                       null,
                                       sel,
                                       selArgs,
                                       CallLog.Calls.DEFAULT_SORT_ORDER);
                break;

            default:
                Log.w(TAG, "Unhandled id: " + Integer.toString(id));
                break;
        }

        return ret;
    }

Once this callback returns, the LoaderManager for our Activity has an instance of the Loader and will tell it to load data at the appropriate time. Since we’re using a built-in Loader, we won’t get to directly see when it is operating in another thread.  However, once the data loading is complete, our Activity is notified via another callback method: onLoadFinished(). It is at this point we can take the new data and do something with it. In our case, all we need to do is take the Cursor and swap it into our Adapter. The Adapter and ListView will do the rest and ensure the UI is up to date with this latest data.

    @Override
    public void onLoadFinished(Loader loader, Object data) {
        Log.d(TAG, "onLoadFinished for id: " + Integer.toString(loader.getId()));

        //  We only have the single Loader, so no need to verify ID
        mAdapter.swapCursor((Cursor)data);
    }

Something else worth mentioning here is that the Loader is automatically listening to the data source for updates. In our sample code, if a new call arrives and is handled so it makes it into the call log, we don’t have to do anything special to have our UI updated. The Loader is already setting an observer on the data source, so when the data changes it pulls the new data and will call our onLoadFinished() again. Notice that we did not close the previous Cursor object. The Loader is automatically releasing the backing data for us as well. Talk about convenient!

Android Loader - Received Calls

Gotchas

What would a framework API be without some gotchas? Especially ones which deal with multiple contexts of execution! While the Loader handles our data and background data handling, they still must be handled with care.  Here is a brief list of potential pain points, in no particular order:

  • It is generally not allowed to do Fragment transactions from within the onLoadFinished() callback.  This is because the method can actually be called after Activity.onSaveInstanceState(). It’s an interesting race condition, but ultimately still up to you to handle. You could easily send a message to a Handler bound to the main thread and have it handle the Fragment transaction as long as the Activity is in the running state.
  • Depending on when you called initLoader(), the load may finish before UI components are initialized. I’ve seen this happen with apps using Fragments in particular.
  • Like the first bullet, you’ll need to be careful directly manipulating UI components from within onLoadFinished() as the Activity or Fragment may no longer be in the running state and could result in an exception.
  • Any time onLoadFinished() is called you must discard any previous data provided by the Loader. The new data being provide replaces it. However, don’t release the original resource (such as calling Cursor.close()) as the Loader will handle the final cleanup of old data.
  • Remember that the Loader is already observing the data source for changes, so there is no need to set a data change observer for the data source (such as ContentResolver.registerContentObserver()).

 

Compatibility with pre-API 11

As mentioned previously, it is still possible to use an Android Loader on targets running Android versions prior to Honeycomb. To do this, use the appcompat library v4 and derive your Activity from FragmentActivity. You can then use the android.support.v4.app.LoaderManager, android.support.v4.content.AsyncTaskLoader or android.support.v4.content.CursorLoader.

 

Wrap Up

The Android Loader API enables app authors more robust handling of data loading off the main thread. Unlike manually managing threads and coordinating with Activity or Fragment lifecycle, the LoaderManager is tied to these core components and do much of the heavy lifting for us. Like most of the Android framework, the Loader API is constructed to be generic so you can extend it for your own needs. The key to understanding the Loader mechanism is understanding the objects involved and how/when they are called. So the next time you need to load any type of data or resource, consider easing your development efforts and debug pain level by using Loaders.

The sources used in this article can be found at the following GitHub repository:

https://github.com/hiq-larryschiefer/BasicLoader.git