This post introduces the LoaderManager class. This is the second of a series of posts I will be writing on Loaders and the LoaderManager:
- Part 1: Life Before Loaders
- Part 2: Understanding the LoaderManager
- Part 3: Implementing Loaders
- Part 4: Tutorial: AppListLoader
Note: Understanding the LoaderManager requires some general knowledge about how Loaders are work. Their implementation will be covered extensively in my next post. For now, you should think of Loaders as simple, self-contained objects that (1) load data on a separate thread, and (2) monitor the underlying data source for updates, re-querying when changes are detected. This is more than enough to get you through the contents of this post. All Loaders are assumed to be 100% correctly implemented in this post.
What is the LoaderManager?
Simply stated, the LoaderManager is responsible for managing one or more Loaders associated with an Activity or Fragment. Each Activity and each Fragment has exactly one LoaderManager instance that is in charge of starting, stopping, retaining, restarting, and destroying its Loaders. These events are sometimes initiated directly by the client, by calling initLoader(), restartLoader(), or destroyLoader(). Just as often, however, these events are triggered by major Activity/Fragment lifecycle events. For example, when an Activity is destroyed, the Activity instructs its LoaderManager to destroy and close its Loaders (as well as any resources associated with them, such as a Cursor).
The LoaderManager does not know how data is loaded, nor does it need to. Rather, the LoaderManager simply instructs its Loaders when to start/stop/reset their load, retaining their state across configuration changes and providing a simple interface for delivering results back to the client. In this way, the LoaderManager is a much more intelligent and generic implementation of the now-deprecated startManagingCursor method. While both manage data across the twists and turns of the Activity lifecycle, the LoaderManager is far superior for several reasons:
startManagingCursormanages Cursors, whereas the LoaderManager managesLoader<D>objects. The advantage here is thatLoader<D>is generic, whereDis the container object that holds the loaded data. In other words, the data source doesn't have to be a Cursor; it could be aList, aJSONArray... anything. The LoaderManager is independent of the container object that holds the data and is much more flexible as a result.Calling
startManagingCursorwill make the Activity callrequery()on the managed cursor. As mentioned in the previous post,requery()is a potentially expensive operation that is performed on the main UI thread. Subclasses of the abstractLoader<D>class, on the other hand, are expected to load their data asynchronously, so using the LoaderManager will never block the UI thread.startManagingCursordoes not retain the Cursor's state across configuration changes. Instead, each time the Activity is destroyed due to a configuration change (a simple orientation change, for example), the Cursor is destroyed and must be requeried. The LoaderManager is much more intelligent in that it retains its Loaders' state across configuration changes, and thus doesn't need to requery its data.The LoaderManager provides seamless monitoring of data! Whenever the Loader's data source is modified, the LoaderManager will receive a new asynchronous load from the corresponding Loader, and will return the updated data to the client. (Note: the LoaderManager will only be notified of these changes if the Loader is implemented correctly. We will discuss how to implement custom Loaders in part 3 of this series of posts).
If you feel overwhelmed by the details above, I wouldn't stress over it. The most important thing to take away from this is that the LoaderManager makes your life easy. It initializes, manages, and destroys Loaders for you, reducing both coding complexity and subtle lifecycle-related bugs in your Activitys and Fragments. Further, interacting with the LoaderManager involves implementing three simple callback methods. We discuss the LoaderManager.LoaderCallbacks<D> in the next section.
Implementing the LoaderManager.LoaderCallbacks<D> Interface
The LoaderManager.LoaderCallbacks<D> interface is a simple contract that the LoaderManager uses to report data back to the client. Each Loader gets its own callback object that the LoaderManager will interact with. This callback object fills in the gaps of the abstract LoaderManager implementation, telling it how to instantiate the Loader (onCreateLoader) and providing instructions when its load is complete/reset (onLoadFinished and onLoadReset, respectively). Most often you will implement the callbacks as part of the component itself, by having your Activity or Fragment implement the LoaderManager.LoaderCallbacks<D> interface:
public class SampleActivity extends Activity implements LoaderManager.LoaderCallbacks<D> {
public Loader<D> onCreateLoader(int id, Bundle args) { ... }
public void onLoadFinished(Loader<D> loader, D data) { ... }
public void onLoaderReset(Loader<D> loader) { ... }
/* ... */
}
Once instantiated, the client passes the callbacks object ("this", in this case) as the third argument to the LoaderManager's initLoader method, and will be bound to the Loader as soon as it is created.
Overall, implementing the callbacks is straightforward. Each callback method serves a specific purpose that makes interacting with the LoaderManager easy:
onCreateLoadersimply returns a newLoader. The LoaderManager will call this method when it first creates the Loader.onLoadFinishedis called automatically when a Loader has finished its load. This method is typically where the client will update the application's UI with the loaded data. The client may (and should) assume that new data will be returned to this method each time new data is made available. Remember that it is the Loader's job to monitor the data source and to perform the actual asynchronous loads. The LoaderManager's job is to simply receive these loads once they have completed, and to then pass the result to the callback object'sonLoadFinishedmethod for the client (i.e. the Activity/Fragment) to use.Lastly,
onLoadResetis called when the a Loader's data is about to be reset. This method gives you the opportunity to remove any references to old data that may no longer be available.
In the next section, we will discuss a commonly asked question from beginning Android developers: how to transition from outdated managed Cursors to the much more powerful LoaderManager.
Transitioning from Managed Cursors to the LoaderManager
The code below is similar in behavior to the sample in my previous post. The difference, of course, is that it has been updated to use the LoaderManager. The CursorLoader ensures that all queries are performed asynchronously, thus guaranteeing that we won't block the UI thread. Further, the LoaderManager manages the CursorLoader across the Activity lifecycle, retaining its data on configuration changes and directing each new data load to the callback's onLoadFinished method, where the Activity is finally free to make use of the queried Cursor.
public class SampleListActivity extends ListActivity implements
LoaderManager.LoaderCallbacks<Cursor> {
private static final String[] PROJECTION = new String[] { "_id", "text_column" };
// The loader's unique id. Loader ids are specific to the Activity or
// Fragment in which they reside.
private static final int LOADER_ID = 1;
// The callbacks through which we will interact with the LoaderManager.
private LoaderManager.LoaderCallbacks<Cursor> mCallbacks;
// The adapter that binds our data to the ListView
private SimpleCursorAdapter mAdapter;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String[] dataColumns = { "text_column" };
int[] viewIDs = { R.id.text_view };
// Initialize the adapter. Note that we pass a 'null' Cursor as the
// third argument. We will pass the adapter a Cursor only when the
// data has finished loading for the first time (i.e. when the
// LoaderManager delivers the data to onLoadFinished). Also note
// that we have passed the "0" flag as the last argument. This
// prevents the adapter from registering a ContentObserver for the
// Cursor (the CursorLoader will do this for us!).
mAdapter = new SimpleCursorAdapter(this, R.layout.list_item,
null, dataColumns, viewIDs, 0);
// Associate the (now empty) adapter with the ListView.
setListAdapter(mAdapter);
// The Activity (which implements the LoaderCallbacks<Cursor>
// interface) is the callbacks object through which we will interact
// with the LoaderManager. The LoaderManager uses this object to
// instantiate the Loader and to notify the client when data is made
// available/unavailable.
mCallbacks = this;
// Initialize the Loader with id '1' and callbacks 'mCallbacks'.
// If the loader doesn't already exist, one is created. Otherwise,
// the already created Loader is reused. In either case, the
// LoaderManager will manage the Loader across the Activity/Fragment
// lifecycle, will receive any new loads once they have completed,
// and will report this new data back to the 'mCallbacks' object.
LoaderManager lm = getLoaderManager();
lm.initLoader(LOADER_ID, null, mCallbacks);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// Create a new CursorLoader with the following query parameters.
return new CursorLoader(SampleListActivity.this, CONTENT_URI,
PROJECTION, null, null, null);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// A switch-case is useful when dealing with multiple Loaders/IDs
switch (loader.getId()) {
case LOADER_ID:
// The asynchronous load is complete and the data
// is now available for use. Only now can we associate
// the queried Cursor with the SimpleCursorAdapter.
mAdapter.swapCursor(cursor);
break;
}
// The listview now displays the queried data.
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
// For whatever reason, the Loader's data is now unavailable.
// Remove any references to the old data by replacing it with
// a null Cursor.
mAdapter.swapCursor(null);
}
}
Conclusion
As its name suggests, the LoaderManager is responsible for managing Loaders across the Activity/Fragment lifecycle. The LoaderManager is simple and its implementation usually requires very little code. The tricky part is implementing the Loaders, the topic of the next post: Implementing Loaders (part 3).
Leave a comment if you have any questions... or just to let me know if this post helped or not! Don't forget to +1 this blog in the top right corner too! :)

Yeah it did help :-) Decent Android tutorials that go beyond 'hello world' and don't encourage readers to write unmaintainable crap are few and far between. Thanks!
ReplyDeleteReally helpful for aspiring developers, looking forward to the follow-up, Thanks
ReplyDeleteThank you, this was helpful ... and a good idea to separate the concerns of loading the data so clearly, by posting about the LoaderManager first
ReplyDeleteDavid
Very helpful tutorial. I have one doubt regarding this, if you can clarify. Can we create an adapter that is directly linked to database??? means whatever changes i have made to the database should be reflected to the adapter. Suppose i have inserted 2 rows in a table then the adapter should be refreshed and two more items should added to it. Is this possible using SimpleCursor Adapter or CursorLoader ????
ReplyDelete[original post on August 20, 2012 8:44 AM]
DeleteAndroid adapters use container objects to hold their data... ArrayAdapter uses an ArrayList, CursorAdapter uses a Cursor, etc. Therefore it is not possible to directly link your adapter with the database... there has to be a step in between.
"If you really wanted, you could also instantiate an anonymous class and hold a reference to the callbacks object in a private instance field like so (there's no reason to ever do this and just makes things more confusing in my opinion (...)"
ReplyDeleteAnd how would you instantiate multiple Loaders in one Activivty? This is the way I do it, so I'd be glad to get to know a better way.
[original post on September 4, 2012 3:24 AM]
DeleteI should probably just take out that "anonymous class" thing... it might be more confusing than it is helpful.
To instantiate multiple Loaders in one activity, you could have your Activity implement the LoaderManager.LoaderCallbacks (thus making your Activity the callbacks object that the LoaderManager will be interacting with), and then for each Loader you create, call initLoader(id, null, this). In order to distinguish which Loader is being created/loaded/reset, you could do a switch case on the Loader's id in the Activity's callback methods (i.e. by calling getId()). This is how I would do it at least... I just think it is a little bit easier to understand. :)
Thx for this great summary about loaders. Especially the part about the Loader ID helped me fixing a problem when managing Loaders from different Fragments!
ReplyDeleteHi Alex, nice tutorial by the way. May I ask how did you define the getLoaderManager() Method? When I tried this in my project, I'm having this error:
ReplyDeleteThe method getLoaderManager() is undefined for the type SearchByNameProject.
Sorry Today was my first time to use a LoaderManager.
Thank you.
Hi may I also add what value did you assign for the CONTENT_URI? Thank you.
ReplyDelete[original post on September 20, 2012 7:06 AM]
DeleteAre you using the support package? If eclipse is failing to recognize getLoaderManager() it could be that it wants you to use getSupportLoaderManager() instead (i.e. if you are using a FragmentActivity instead of an Activity).
Are you familiar with ContentProviders at all? Understanding what a "CONTENT_URI" is assumed throughout this post, but basically it's a Uri constant that you have to use in order to ask the ContentResolver to query your particular ContentProvider. Try reading the article on ContentProviders on the Android developers site... it's very good. And don't give up on this stuff... it's hard to grasp at first (I went through the same thing about a year ago) but it's worth understanding in the end ;)
I don't think you answered the actual question. What was your CONTENT_URI?
DeleteIt would have been something like this: "content://com.package.name/table_name";
DeleteCheck out this link for more information.
Great tutorial!
ReplyDeleteIs there a way my onLoadFinished() can react differently depending on whether it's called from a data update or not?
I am also getting the error:
ReplyDeleteThe method getLoaderManager() is undefined for the type MainActivity
[original post on October 7, 2012 11:34 AM]
DeleteThings to try:
(1) Post a question on StackOverflow... it'll get answered faster than you think :)
(2) If you are using the support package, make sure you are extending FragmentActivity
(3) If you are using the support package, delete all of your import statements and re-import with "Ctrl + Shift + O" and make sure you select only the support package libraries.
(4) If you are using the support package with a FragmentActivity, make sure you are using "getSupportLoaderManager()"
(5) If you are using the support package with a Fragment, make sure you are using "getLoaderManager()"
(6) Run Android lint... it might help pin point the specific problem you're having.
Thanks a lot. Better than the official documentation!!!
ReplyDeletehi Alex , thanks for tutorial , now after implementing LoaderManager in my application , i understand that loaderManager is very2 awesome and helpful . .
ReplyDeleteand you're right "it's hard to grasp at first but it's worth understanding in the end ;)" ,
Thanks. ..
No problem, glad I could help!
DeleteThe loader is crappy and works unpredictable especially on config changes. Though it looks good it's logic is not as expected and (surprise!) is not documented. This crap that Google may call documentation is just some useless comments which are obvious. One must trace into its source code to understand how the fck it works!
ReplyDeleteThat was the goal of these posts... to understand how the f*ck it works. Hopefully it helped at least a little. :)
DeleteBy the way, if you think the documentation is bad now... good thing you weren't around when it first came out. The documentation was absolutely terrible... :/
10x Alex, your blog is excellent and this article is especially good and helpful! I was really frustrated that Loader didn't help in my case, but causing me problems instead. And not just this but some defects of ArrayAdapter came in to play and the mess was incredible. Actually I did understand loaders a bit wrong. I didn't expect that they rely very much on the observer/observable (which i had not implemented) Without a call to onContentChanged() it reloads some old data on config change. In fact it is not just a loader (i erroneously assumed that it's purpose is to load the AT START, but it serves the data all the time). When I added observer stuff and call to onContentChanged() on each single data change across the activity - everything got working! In my case problem was a bit more complicated because I implemented my own loader that works with a result object that packs the data + [optional] exception that maybe is cought inside loadInBackground(). If anyone is interested:
ReplyDeletehttp://pastebin.com/CX1nxBhp
http://pastebin.com/1MaaJnht
Hi Alex, would you confirm one thing? I followed your examples to create a cursor loader, but it wasn't recognizing and auto-updating my screen with changes from the db. I finally came across a comment somewhere that suggested you must also go into you content provider's query method and add a call to cursor.setNotificationUri. It is working now which is nice, but I want to make sure adding this call is necessary, instead of a hack that masks some other problem.
ReplyDeleteIt isn't a hack. You need to do that in order to have these notifications delivered to the cursor. :)
DeleteAlex you have literally saved my life. Thank you.
ReplyDeleteWould really be helpful if you showed you AndroidManifest.xml file too
ReplyDeleteSee part 4 of this series for a complete example. Not sure why you need to see it though... Loaders don't require any extra configuration or setup in the manifest...
Delete