Asynchronous Background Execution and Data Loading with Loaders (Framework) in Android

Android has this Loader framework that offers a powerful (yet simple) way to asynchronously load data from content providers or other data sources like an SQLite database, network operation, et al. in an Activity or a Fragment (clients).

Loaders have already been well explained by the documentation and some other guys, hence I’d highly recommend you to go through these articles:

What's the one thing every developer wants? More screens! Enhance your coding experience with an external monitor to increase screen real estate.

Although these articles are enough, I’ll still try to summarize (and simplify) the entire concept and include certain points that might not be mentioned in those links.

Introduction

So what you basically do is, connect to the Loader framework from a client which can be either an Activity or a Fragment. This happens by requesting a loader from the LoaderManager class. The loader manages a connection with the data source. Android has a CursorLoader class for when the data source is a ContentProvider, so it queries the ContentResolver and returns a Cursor. For any other type of data source, you’ll have to code up a custom loader which ain’t too hard, but requires a deeper understanding of the Loader framework.

Some of the characteristics or features offered by Loaders are:

  • They interact with the data source in a background thread to provider asynchronous loading of data. Once data is available, the loader triggers a callback (generally in the UI thread) with the data that can be used to modify the user interface for instance.
  • They’re available to every Activity and Fragment and plays well with their respective lifecycles. This means if the Activity or Fragment is stopped (or destroyed), the loaders will also stop.
  • Loaders can monitor (observe) the data source to deliver/update new results when the underlying data/content changes.
  • On a configuration change, for eg. an orientation change, loaders that were running in the background continue to do their work. Then they retain their state over a configuration change so that they don’t have to re-query their data and delivers their cached data to the recreated Activity. They operate on `Application` context, hence the Activity `Context` object undergoing a configuration change is not preserved causing memory leak.

The entire framework consists of these 4-5 important classes/interfaces:

LoaderManager

LoaderManager is an abstract class that manages all the loaders used by an Activity or a Fragment (clients). A LoaderManager can have multiple loaders but there’s only one LoaderManager per client. LoaderManager is like a mediator between a client and its loaders. To get a LoaderManager object, you don’t instantiate it but call Activity.getLoaderManager() or Fragment.getLoaderManager().

Now there are two important methods that the LoaderManager API exposes:

These two methods basically creates new loaders to use and has certain differences. But before diving deep into them, we must first go through the LoaderManager.LoaderCallbacks interface (which is also the third argument passed to these methods).

LoaderManager.LoaderCallbacks

For the clients to interact with the LoaderManager to create loaders and handle the data loaded by the loaders, they need to implement the LoaderManager.LoaderCallbacks interface. Here’s a basic Activity that doesn’t do anything but shows how to implement LoaderCallbacks.

public class LoadersActivity extends Activity implements LoaderManager.LoaderCallbacks<D> {

    @Override
    public Loader<D> onCreateLoader(int id, Bundle args) {
        return null;
    }

    @Override
    public void onLoadFinished(Loader<D> loader, D data) {

    }

    @Override
    public void onLoaderReset(Loader<D> loader) {

    }
}

The loader is defined as Loader<D> where <D> is the data type returned by the loader and also passed to onLoadFinished(). Now when LoaderManager.initLoader() is called with a unique identifier, if there is no loader available with that ID then onCreateLoader() callback is invoked so that a new loader can be created and returned to the LoaderManager. The LoaderManager can now start dealing with the loader’s lifecycle and loading data from the data source via the loader. On the other hand, is a loader with that ID already exists, then there is no need to create a new loader as the old one is cached. Also when the initialization is done through initLoader(), the background data loading is initiated.

Once the loader is initialized and the background data loading is completed, the result is delivered to the LoaderManager which passes it to the client’s onLoadFinished() callback on the UI thread so that the UI components can be updated. This callback is also called if initLoader() is called when a loader already exists or the data source has updated its content and the loader is observing it for changes. The client can also force an asynchronous data load by calling Loader.forceLoad().

Clients can cancel initiated background loads with Loader.cancelLoad(). If the load has already started (background execution) then the results will be discarded and not delivered to the client.

Once a previously created loader is no longer available because maybe the client was destroyed or LoaderManager.destroyLoader(id) was called, then the client is notified in the onLoaderReset() callback where it should free up resources by removing all old references.

initLoader()

This method initializes a loader with the given ID (first argument). This identifier must be unique for all loaders within the same client. Note: An Activity and a Fragment can have loaders with the same numbers (identifiers) without any interference.

Now if no loader exists with the specific ID, then a new loader is fetched from onCreateLoader() after which background data load is initiated and the returned result is delivered to onLoadFinished(). If a loader already exists, then onLoadFinished() is called with the latest loaded data (cached version). So with initLoader() we create a loader and get data or just retrieve the cached result from an existing loader. This same loader is reused to retrieve cached results after a configuration change too (screen rotation for instance).

restartLoader()

The restartLoader() will never reuse existing loaders. They always destroy the existing loader (along with its data) with the specified ID and creates a new loader by invoking the onCreateLoader() callback. This also initiates a completely new data load. There’s no question of data caching in this case either. You must be wondering why one would use this. This method is useful if you’ve a search bar where the user types a keyword to search. In such a case, the data source will need to be queried and a new result set will be returned for every search term.

Both initLoader() and restartLoader() accepts a second argument as a Bundle that is passed to LoaderCallbacks.onCreateLoader().

Loader and AsyncTaskLoader

The Loader framework has a Loader class that basically represents a loader performing asynchronous loading of data. This class is not very interesting as you wouldn’t use it nor derive another class from it really. Instead it has a subclass called AsyncTaskLoader which uses an AsyncTask to perform the data loading work in a background thread. It relies on AsyncTask.executeOnExecutor() to perform its background execution. This is the class that should be extended to write your own custom loaders.

The AsyncTaskLoader tries to keep the number of active threads to a minimum. So several consecutive forceLoad() calls from the client might delay the result’s delivery to onLoadFinished(). Infact AsyncTaskLoader will cancel all the previous loads to invoke only the newest load. This means not only will you not get all the results in your callback but also the last one’s might delivery might get delayed.

A Loader with an observer that’s observing the underlying data set will trigger multiple background loads for multiple changes, which’ll also call onLoadFinished() multiple times that updates the UI components (redraw). This might lead to an unresponsive laggy user interface. This can be prevented by throttling the data load to occur after a delay using setUpdateThrottle().

CursorLoader

The Loader framework has the CursorLoader class that subclasses AsyncTaskLoader. This class can be used to load Cursor objects from ContentProvider data sources only. It’s actually a loader with the Cursor data type (extends AsyncTaskLoader<Cursor>). CursorLoader itself manages the lifecycle of the Cursor object too, so for example no need to close it in your application code. Note: It cannot be used with SQLite databases, for that you’ll need to code your own custom loader class.

CursorLoader registers Loader.ForceLoadContentObserver which is actually a ContentObserver, on the Cursor object to detect changes in the underlying data set (content provider). Internally this is done by creating an instance of ForceLoadContentObserver and passing it to cursor.registerContentObserver. The content provider will also need to register the cursor to watch for any data changes pointed by the content URI using cursor.setNotificationUri().

Instead of writing a class that shows how to use CursorLoader and hence shows the usage of loaders, I’d just point you to the last huge piece of code in the documentation which is fairly simple to understand. Notice how that piece of code uses swapCursor() but not changeCursor() – the latter also closes the old cursor object.

One thing you might still be wondering is that, from that code sample you learnt how to query/retrieve data from the Content Provider, but what about changing data, i.e., insert, update and delete (other CRUD operations). For those operations, I don’t know if there’s a recommended solution but you could possibly use an AsyncTask inside your Activity or even better make use of AsyncQueryHandler. Your AsyncQueryHandler code will remain separate from anything related to loaders/CursorLoader – no mix up.

Implementing Custom Loaders

Most of the Loaders examples you’ll come across will be in conjunction with content providers because that is supported by Android out of the box (via CursorLoader). If you want your data source to be something other than a content provider, like a file or a SQLite database directly, you’ll have to write a custom loader by generally extending AsyncTaskLoader. This task is not simple and requires quite a bit of understanding of how the Loader framework works internally, or just the CursorLoader and AsyncTaskLoader. Let’s cover various concepts that’ll help us with the process.

States of a Loader

Loader is the base class for any loader. A loader can be in four different states that is held (instance variables) by Loader.

  • Reset – In this state, the loader gives up any data associated with it for garbage collection. This state is achieved by calling the reset() method that calls the onReset() callback that you must implement in your subclass and free up resources. Called by the LoaderManager when destroying the loader.
  • Started – This is the started state achieved by a call to startLoading() that’ll invoke the onStartLoading() callback that you must override where you shall start loading the data by making a call to forceLoad() that invokes the onForceLoad() callback on the AsyncTaskLoader that in turn will call loadInBackground() on a worker thread where you do your background loading operations. Monitoring for changes and performing new loads based on the changes will also be done here. This is the only state in which onLoadFinished() is called (generally in the UI thread).
  • Stopped – In this data, no data can be delivered to the client (that can only happen in the Started state). It may observe/monitor for changes and load content in the background for the purpose of caching that can be used later if the loader is started again. From this state the loader can be started or reset.
  • Abandoned – An intermediary state between stopped and reset where it holds the data until a new loader is connected to the data source, so that the data is available until the restart is completed.

The client (Activity/Fragment) need not call any of the methods to change the loader’s state, it’s all handled by the LoaderManager. All the client has to care about is calling initLoader() and restartLoader(). Ofcourse it can call forceLoad() to just trigger a new data load. This should be done in the started state else no data will be delivered to onLoadFinished().

Loading Data in Background

As mentioned earlier, your custom loader should extend AsyncTaskLoader which has a method called loadInBackground() which is what you must override. This method is invoked on a worker thread where all your long task should be executed. Here’s a simple custom loader demonstrating this:

class CustomLoader extends AsyncTaskLoader<String> {
    public CustomLoader(Context context) {
        super(context);
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();

        forceLoad();
    }

    @Override
    public String loadInBackground() {
        return loadData();
    }

    private String loadData() {
        SystemClock.sleep(2000);
        return "Hello World";
    }
}

Once the loadInBackground() method returns the data, the AsyncTaskLoader calls deliverResult() (with that data) that you should override and from it should call super.deliverResult() which basically calls the Loader.deliverResult() which forwards the results to registered OnLoadCompleteListener<D>. The LoaderManager registers an OnLoadCompleteListener<D> for each of its loaders which forwards the results received from that Loader.deliverResult() call to the client’s onLoadFinished() implementation.

Observe Underlying Data Set

Your loader should observe the underlying data set just like CursorLoader does, so that on data change it can initiate a new data load request in the background. In the case of CursorLoader, ContentObserver has been used internally. But apparently that’s not possible with say direct SQLite cursors without dirty hacks. Here are some helpful SO answers on it:

So as mentioned in the first answer make use of a LocalBroadcastManager or EventBus library. Event based approaches are lovely, so feel free to use that library. This article even explains how to use it.

Some other solutions are using Observer and Observable. Here’s an example. You can use BroadcastReceiver that works across processes or just use LocalBroadcastManager locally within the application. There’s the FileObserver class to monitor filesystem changes.

When your observer is updated with a change notification, you’ll want to load new data asynchronously again. This can now be done in two ways:

  • Call forceLoad() regardless of the loader’s state, hence it’s up to you whether you want to check for the started state or not (isStarted returns true).
  • Trigger Loader.onContentChanged() which is generally called when Loader.ForceLoadContentObserver detects a change, i.e., its onChange() method is called. You call onContentChanged() directly that initiates data loading (by calling forceLoad()) if the loader’s state is started. If not started, it sets a flag so that takeContentChanged() returns true and reset the flag to false. So takeContentChanged() basically indicates whether the loader’s content had changed while it was stopped.

So based on what we learnt about takeContentChanged(), your custom loader’s onStartLoading() should look more like this:

@Override
protected void onStartLoading() {
    super.onStartLoading();

    if (data == null || takeContentChanged()) {
        forceLoad();
    }
}

Caching Results

It’s always a good idea to cache results fetched from loadInBackground() so that further calls to onStartLoading() when initLoader() is called doesn’t lead to loading data again in a background thread, hence saving resources. This can be done by caching the results in deliverResult() and then the new onStartLoading() would look like this:

@Override
protected void onStartLoading() {
    super.onStartLoading();

    if (data != null) {
        deliverResult(data);
    }

    if (data == null || takeContentChanged()) {
        forceLoad();
    }
}

That was a really long explanation of the intricacies of the Loader framework. Now that you know how it works, and what you have to do, why not try building a custom loader as a challenge and share in the comments below ? If you struggle with it or need further help, feel free to check out the CursorLoader source code here.

Loaders for Long Running Background Tasks

For super long running background tasks like a network operation, don’t use Loaders as they’re strictly attached to their Activity/Fragment context. If the Activity or Fragment is destroyed, they’ll also be destroyed. It’s better to make use of an IntentService in such a case which also executes on a background thread and is not tied to its client component. It can be also started with various flags to restart, if killed by the system.

Conclusion

We covered almost everything about Loaders. In case of confusion you should always go through the source code of various classes like AsyncTaskLoader, Loader, CursorLoader, etc. If you’re working with content providers or similar data sources, then do consider using them as they solve various problems really efficiently with a neat API. Got questions ? Ask in the comments below.

Recommended from our users: Dynamic Network Monitoring from WhatsUp Gold from IPSwitch. Free Download

Author: Rishabh

Rishabh is a full stack web and mobile developer from India. Follow me on Twitter.

Leave a Reply

Your email address will not be published. Required fields are marked *