Asynchronous Tasks with Loaders in Android

Henrick Kakutalwa
5 min readApr 3, 2018

--

Introduction

Years ago, Android developers were used to AsyncTasks to leverage asynchronous work in order to prevent freezing the UI. But AsyncTasks are not recommended if you are invoking asynchronous work directly in your activity or fragment code.

When you initialize AsyncTasks in your UI code, you’ll probably end up having problems with the activity lifecycle. If in some circumstance - screen orientation change for example - your activity is destroyed, the task continues to run and therefore holds the old dead activity in memory.

Now imagine that you’re constantly invoking tasks, multiple times in short period of times, summing all that with constant activity lifecycle due to user engagement in your app or OS behavior. That could end up being very bad because your app will accumulate pending tasks, “zombie activities” or worse.

You could say that the remedy for that problem is just using services in order to detach your tasks from UI lifecycle. That’s a completely viable solution, but you don’t want to always detach your tasks from your UI, sometimes you can even prefer to create tasks very attached to your activities.

If you want to synchronize data to your local SQLite database from time to time, it’s recommended to use services (job services to be more specific). But you want to load data and run tasks as your user navigates and interacts with your app, maybe Loaders are the best option.

How AsyncTaskLoader works?

We’ll use AsyncTaskLoader, which is a specific implementation of the Loader class that uses internally a AsyncTask to run our tasks, but like every other Loader, it adapts itself to the lifecycle of our UI code.

Let’s best understand that, AsyncTaskLoader allows you to run tasks that will survive through activity lifecycle changes, meaning that if your activity/fragment is destroyed while your task is running, the recreated activity will still stick to your running (or work finished) task, unlike the normal AsyncTask.

Analyzing the sample code

You can check or download the source code for the sample app here.

The sample app just retrieve a list containing Github users from Github API, and shows them in a RecyclerView. Let’s study the code!

Initializing the Loader

In our MainActivity onCreate method we have the following implementation:

We initialize the activity, Retrofit (to make web requests) and our recycler view. At the last line we initialize and start our loader through the support loader manager. The loader manager it’s just a class that help us manage all loaders in our application, start, restart them, and if needed (rarely needed) destroy them.

Notice also that usually loaders have ids, which are basically integer numbers that uniquely identify loaders throughout our application. When we call initLoader it does the following tasks:

  • It allows you to check if the loader has cached data that was retrieved from the work or task (network api, database).
  • If there is cached data, it gives you that data.
  • If there isn’t cached data, it will run our network request to get the data (or database query, it depends of what you’re doing).

This procedure will allow us to not repeat unnecessarily network requests if the data that we fetched previously is already available, thus preventing excessive resources usage.

It’s very important to know that if the data is retrieved after your activity is killed for some reason, initLoader will deliver that data to your brand new restarted activity.

And finally check the last argument passed into initLoader. With that argument we are defining our activity as the callback that the loader manager will use to deliver the results of the intended task. More on this later.

Restarting the Loader

Look to this section of code:

This method is called when you tap the refresh button, it basically uses the loader manager to restart our loader. So, what’s the difference from the previous initLoader?

The answer is very simple, restartLoader just ignores any cached data that our loader may contain and run directly the task.

Loader callbacks

In the source code, you can also note that our MainActivity implements the interface LoaderManager.LoaderCallbacks<T>, where T it’s the type of the data that will be returned after our task is completed, in this case we’ll return a list of users (List<User>).

The callback interface has two callback methods:

  • onCreateLoader: here we create our inner AsyncTaskLoader object with the task that we want to execute, and return that to the loader manager.
  • onLoadFinished: called by the loader manager with the retrieved data when the task is completed. We usually present the data in our UI inside this method.
  • onLoadReset: we will not use this method.

onStartLoading contain tidy up code, and loadInBackground contains our actually work or blocking task code, this code is executed in another thread.

Notice in the code above that when we implement AsyncTaskLoader’s onStartLoading we check to see if there’s cached data. In order to avoid network and battery resources waste by doing another network request, we just deliver that cached data to onLoadFinished, otherwise we run our task.

When we start our loader with initLoader, the cached data is preserved if a previous task was succesfuly completed, so we are able to check if it’s there. Another case is starting our loader with restartLoader, that destroys that cached data, thus forcing the execution of the task.

And finally we have onLoadFinished, where the loader manager delivers the result of our data (cached or not) and we can show that data in our UI. onLoadFinished will executed in the recreated activity if the old one (the zombie activity) is destroyed due to lifecycle.

Conclusion

In this brief article we talked about a problem inherent with AsyncTask, in a environment where activities are constantly created and destroyed, it can create memory leaks by holding the activity attached to it, there’s also the possibility of phone resources misuse (battery and network) due to accumulated tasks contained in “zombies activities”. So, AsyncTask have problems with activity lifecycles.

We can choose to use services to run tasks that are detached from the ui lifecycle, but not every task is intended to be detached from the UI. So, we use Loaders, which are a way to create UI attached tasks that don’t suffer from activity lifecycle problems that AsyncTask have.

--

--