The idea of Loaders never really caught on. They were introduced with Honeycomb around 2011. It was a solution to a problem that we shouldn’t have really been trying to solve — running asynchronous tasks within a Fragment or Activity and handling the rotation change. A Fragment or View should just represent the state of your application and handle the interaction — not run any asynchronous business logic.
They are still part of the official Android API guide.
So what were they trying to solve?
- Loaders run on separate threads to prevent janky or unresponsive UI.
- Loaders simplify thread management by providing callback methods when events occur.
- Loaders persist and cache results across configuration changes to prevent duplicate queries.
- Loaders can implement an observer to monitor for changes in the underlying data source. For example, CursorLoader automatically registers a ContentObserver to trigger a reload when data changes.
The reality is that none of this is something that would warrant the use of the Loaders today. All of that can be solved without diving into the “black box” LoaderManager stuff.
Persisting and caching results across configuration changes can be solved by using the ViewModel from Architecture Components. They are not affected by orientation change and so you don’t need to bother with it at all. Optionally, you can cancel the task once
onCleared() is called in your ViewModel.
Loaders are tied to the lifecycle of Activities and Fragments and should deliver results only when the component is ready. But again — LiveData are doing the same and more elegantly, respecting the life cycle and delivering results only when the data is changed and someone is listening.
There was a special type of loader — a CursorLoader and it had one “magic” feature: the ability to observe changes in the data thanks to the underlying ContentProvider. But the implementation required a lot of boilerplate code and it had its drawbacks. Besides, ContentProviders are only meant for exposing data to other processes. Room from the Architecture components can return LiveData and you will get notified of changes in the database automatically with 0 lines of code. SQL Brite from Square can do the same. And anyway, Google is now advocating to stop using Cursors because of the inefficient paging and other factors.
Caching and more advanced threading is better off left to RxJava and similar frameworks (and if you don’t need that, then even an AsyncTask inside a ViewModel is more simple and will work the same).
Issues with Loaders
The biggest issue that I have is, that it’s encouraging you to put asynchronous code into your Fragment or UI. Don’t do that.
The API is confusing: I get flashbacks from calling
destroyLoader() at various points in the code to make them work as I want them to. Sometimes you get a cached loader delivered, sometimes a new one is created — maybe I just didn’t dive into the details but I think that their behavior was somewhat unpredictable. There were some bugs (maybe fixed in the support library?) where they would deliver the results too late or too soon.
Google only provided the CursorLoader and AsyncTaskLoader implementation of the Loader class, even though it was meant as a general platform for more extensions in the future. Mark Murphy tried but gave up and wrote a good explanation why. Basically making code that observes data changes across the application isn’t that simple.
They are deeply integrated in the Activity and Fragment source code and can’t be tested with jUnit tests.
Do you know of any relevant and useful features?
Because I don’t. In fact I have seen big projects based on Loaders and it was not pretty. I personally would prefer if Google would deprecate it or at least hide it from the official guidelines. There are also other abandoned parts of the SDK looming around so maybe this isn’t the last article of this kind — please follow me on Medium :-).
Edit: Ian Lake wrote an article that goes into much more detail about migrating from Loaders to LiveData/ViewModel.