If you fetch the data from any ContentProvider in your application, the most likely scenarios are:
- You’re absolutely clueless and fetch the data on UI thread.
- You’re using an
AsyncTaskand:- Your app crashes on screen orientation change.
- You block the screen orientation, because you googled this “solution” on StackOverflow.
- You wrote error prone boilerplate code to detach and reattach the
AsyncTaskfrom the Activity.
- You are using
CursorLoader.
But accessing ContentProvider is probably not the only type of asynchronous operations you perform. You might want to access SharedPreferences, read a file or query a web API. In that case you need Loader<SomeOtherDataThanCursor>, but implementing one correctly is a bit tricky.
I’ll walk you through the entire process of understanding how the Loaders work, implementing a sane base class for your Loaders, implementing CursorLoader with all issues fixed and extending it to allow multiple notification Uris. It’ll be a long post so grab a cup of your favourite beverage.
Loaders 101
Loader should do three things:
- Load data in background thread.
- Cache the loaded data, so you won’t reload it after screen orientation change.
- If applicable, monitor the data source and reload the data when necessary.
The Loader class itself doesn’t provide any mechanism for loading the data in background thread. You either have to implement this yourself, or you can subclass the AsyncTaskLoader. This covers the first point on our requirements list.
The 2nd point is not handled by AsyncTaskLoader. In fact the AsyncTaskLoader is far from being fully functional, for example this perfectly reasonable looking implementation won’t work:
1 2 3 4 5 6 7 8 9 10 | |
AbstractLoader v1
A good starting point for creating either CursorLoader implementation or LoaderCustom.java from SDK samples. Here’s the common part of these two implementations, which provides all necessary boilerplate for loading and caching the data:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 | |
Why this class is not provided by the framework is a mystery to me, but hey, that’s just one more thing you have to know when coding for Android platform. Now you can write your custom Loader like this:
1 2 3 4 5 6 7 8 9 10 | |
But why do we need all that code? The key to understanding the Loaders is understanding the expected Loader’s behaviour in different states: started, stopped, abandoned and reset. Upon entering each state, the appropriate callback is executed:
onStartLoading: the Loader was created and should either load the data or return cached data.onStopLoading: the Loader should keep the cached data and monitor the data source for changes, but it shouldn’t load the data. This happens for example when users presses home button from your app.onAbandoned: someone restarted the Loader. New instance of this Loader was created inonCreateLoadercallback in yourFragment/Activity/whatever and loads new data. The abandoned Loader should keep the data until the new Loader loads and delivers it’s data. There is no point of monitoring data source or reloading the data in abandoned Loader – the data will be loaded by the new instance. When new Loader delivers it’s data this Loader will be reset.onReset: the data previously loaded by this Loader are no longer used and should be cleaned up. This Loader might be started again, so make sure you clean up also any old state in your Loader implementation.
The AsyncTaskLoader provides additional callback:
onCancelled: called after data loading when it turns out that this data is no longer needed, for example when theAsyncTaskexecuting youronLoadInBackgroundwas cancelled. In this callback you should take care of releasing resources.
Since the releasing resources should be also performed in onReset callback and in our deliverResults implementation, our AbstractLoader class provides handy releaseResources() callback for closing your Cursors, file handles, etc.
Now let’s walk through our AbstractLoader implementation. When someone starts our Loader using LoaderManager.initLoader(), the onStartLoading is called:
1 2 3 4 5 6 7 8 9 10 11 12 13 | |
We keep the loaded data in mResult member of our AbstractLoader. If we have already loaded the data, we can just deliver the results to Loader clients. If the cache is empty or the Loader was notified about new data available for fetching, we force data reload by calling forceLoad() method. It starts AsyncTask which executes loadInBackground in background thread and the result is passed to deliverResults function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | |
A lot of interesting things happen here. First, we check if the loader was put into reset state. In this state all the previous resources were already released, so we just need to take care of newly loaded data. Then we swap the data in cache, call deliverResults in Loader and then release the resources for previously cached data.
When the Fragment or Activity with active Loader is stopped, the Loaders are also put in the stopped state. It means that they should keep the cached data, monitor if this data is still valid, but they should not actively load the data or deliver the results to UI thread. In terms of AsyncTaskLoader it means that any running AsyncTasks should be cancelled:
1 2 3 4 | |
Current implementation of AsyncTaskLoader do not interrupt the active tasks, it only marks that the results of these tasks should not be delivered to the UI thread. However, the results might require some resource releasing, so the onCancelled callback is called:
1 2 3 4 5 | |
The last callback we have to implement is onReset:
1 2 3 4 5 6 7 8 9 10 | |
There are two important things here. First, the Loader can be moved to reset state from started state, which means it can still have active AsyncTasks executing loadInBackground. We need to stop them first. Then, as per the specified contract, we have to release the resources and clear the cache.
What about onAbandoned callback? AbstractLoader doesn’t monitor any data source by itself, so this callback doesn’t have to be implemented.
CursorLoader
So how would we implement observing a data source and automatic reloading? Let’s see how would the CursorLoader implementation look like had we used our AbstractLoader as a base class (literally; if you merge MyCursorLoader and AbstractLoader code samples from this post, you’ll get exactly the CursorLoader implementation from support-v4):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | |
This implementation has two bugs and one kind-of-feature. Let’s start with the last one.
onStartLoading contract specifies, that you should start monitoring the data source upon entering this state. But let’s think what would happen if you had a query that takes 200ms to run and your data source would change every 150ms. The loader would never deliver any data, because every load request would be cancelled in middle of loadInBackground execution by our content observer.
I guess that’s why the Android implementation of CursorLoader registers the observer after the data is loaded. This way the first results are delivered as soon as possible, but for subsequent loads the data is delivered only when data didn’t change during loading. I’m not sure if it was intentional, or this behavior was implemented by accident, but it makes sense to me, so let’s adjust our Loaders contract and implement this behavior in our AbstractLoader.
But if you look closely, the CursorLoader implementation violates even this updated contract. Remember that loadInBackground and deliverResults are executed on separate threads. So what would happen if the data observer is triggered after registerContentObserver call, but before the deliverResults? We’d get exactly the same behavior we’d get had we registered the ContentObserver in onStartLoading – the loader would never deliver it’s data. That’s the first bug.
The second issue with CursorLoader implementation is violation of onAbandon callback contract. If someone calls restartLoader and the content observer is triggered, the abandoned Loader instance will happily reload it’s data just to throw it away.
You can dismiss it as something that would happen only 1% of the time and has negligible impact, and if we were talking about application code, I’d agree with you, but IMO library code that will be used by thousands of developers should be held to a higher standard.
Fixing CursorLoader
Here’s the wrap up of changes in behavior:
1. Register ContentObserver after the first data is delivered, not after the first data is loaded.
2. Unregister ContentObserver in onAbandon.
The first point requires changes to deliverResult method, so it makes sense to modify our AbstractLoader:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 | |
Our CursorLoader implementation would look like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | |
The second part – unregistering observer in onAbandon – is tricky. It’s illegal to call Cursor.unregisterContentObserver with observer that wasn’t registered and the onAbandon can be called when the deliverResults wasn’t called (see AsyncTaskLoader.dispatchOnLoadComplete() implementation). One solution would be keeping the set of Cursors that were registered, but it’s not optimal. Instead, we can create a proxy ContentObserver that can be enabled or disabled:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | |
AbstractObservingLoader
The CursorLoader is a bit special case, because the Cursor itself contains ContentObservable. In most cases however the content observers and loaded data would be completely separated. For these cases it would be useful to have a base class for Loader which registers some ContentObservers:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | |
We need to keep the registered state in our Loader, because the default Observable implementation doesn’t like registering the same observer twice or unregistering not registered observer.
Now we can use this class as a base for a Loader which should be reloaded when one of specified Uris is triggered:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | |
Conclusions
I think the Loaders aren’t as bad as some people think and say. Four Loader states might seem complicated at first, but if you think about Android Activity lifecycle they make perfect sense and they are something you’d have to implement yourself, with a high probability of mucking things up. The only thing lacking is documentation and sane base classes for extensions, something I hope I delivered through this blog post.
+CommonsWare wrote few weeks ago that he considers Loader to be a failed abstraction, mostly because the interface assumes there is a single object which notifies the Loader about new data. He concluded his post with the following sentence:
In my case, if I am going to have some singleton manager object, with distinct data objects per operation, I am going to use something more flexible than Loader, such as an event bus.
Extending AbstractObservingLoader to load some data from SQLiteDatabase and subscribe to some event bus for model change events should be trivial, and you’d get a lot of things for free – performing loads in background, cancelling loads, caching results, invalidating cache, and so on.
Having said that, Loaders are not solution to every single problem. They are coupled with activity lifecycle, so they are not suitable for long running tasks that should not be interrupted when user navigates away. In these cases the IntentService, or some other Service implementation is a better choice.


