Replace AsyncTask and AsyncTaskLoader with rx.Observable – RxJava Android Patterns

android-data-binding-library

This blog piece on how to Replace AsyncTask and AsyncTaskLoader with rx.Observable was updated on 10/31/2016 to include sample projects that demonstrate using Observables for AsyncTask style work and also AsyncTaskLoader style work This post was originally published on 2/18/2014.

I’d like to explore some patterns that have emerged as I’ve worked on various projects that rely on RxJava as the primary backbone of the architecture.

There are plenty of “Getting Started” type posts talking about RxJava, some of which are in the context of Android. But at this point, I imagine there are people out there who like what they hear so far but aren’t really sure how or why to use it when solving concrete problems in their Android projects.

To get this journey started, I’m going to tackle some of the most common pain points that Android developers experience and the easy wins that can be expected when using RxJava. From there, I’ll move on to more advanced and/or niche solutions. During this series of posts, I’d love to hear from other devs out there who are solving similar problems using RxJava and how they may differ or match what I’ve discovered.

Problem 1: Background Tasks

One of the first challenges Android developers face is how to effectively do work on a background thread and come back to the main thread to update the UI. This most often arises from the need to fetch data from a web service. Some of you who have been doing this for some time may be saying, “What’s challenging about that? You just fire up an AsyncTask and it does all of the hard work for you.” And if you’re saying this, there’s a good chance it’s because you’ve either (a) gotten used to doing things in an overly complex way and forgot that it could/should be cleaner, or (b) you aren’t handling all of the edge cases that you should be. Let’s talk a little more about this.

Default Solution: AsyncTask

AsyncTask is Android’s default tool for developers needing to do some simple long-ish running work in the context of a UI screen without blocking the UI. (NOTE: More recently, AsyncTaskLoader has come along to help with the more specific task of loading data. We’ll talk more about this later.)

On the surface, it seems nice and simple. You define some code to run on a background thread, you define some other code to run on the UI thread when it’s done, and it handles passing your data to and from each thread.

private class CallWebServiceTask extends AsyncTask<String, Result, Void> {

  protected Result doInBackground(String... someData) {
    Result result = webService.doSomething(someData);
    return result;
  }

  protected void onPostExecute(Result result) {
    if (result.isSuccess() {
      resultText.setText("It worked!");
    }
  }

}

The problem with AsyncTask is that the devil is in the details. Let’s talk through some of these.

Error handling

The first problem that arises from this simple usage is: “What happens if something goes wrong?” Unfortunately, there’s no out-of-the-box solution for this, so what a lot of developers end up doing is subclassing AsyncTask, wrapping the doInBackground() work in a try/catch block, returning a pair of <TResult, Exception> and dispatching to newly defined methods like onSuccess() and onError() based on what happened. (I’ve also seen implementations that just capture a reference to the exception and check it in onPostExcecute().)

This ends up helping a good bit, but now you’re having to write or import extra code for every project you work on, this custom code tends to drift over time, and it’s probably not consistent and predictable from developer to developer and from project to project.

Activity/Fragment lifecycle

Another problem you face is: “What happens if I back out of the Activity or rotate the device while this AsyncTask is running?” Well, if you’re just sending off some fire-and-forget type of work then you might be ok, but what if you are updating the UI based on the result of that task? If you do nothing to prevent it, you will get a NullPointerException and a resulting crash when trying to access the Activity and/or the views since they are now gone and null.

Again, out-of-the-box, AsyncTask doesn’t do much to help you here. As the developer, you need to make sure to keep a reference to the task and either cancel it when the Activity is being destroyed or ensure your Activity is in a valid state before attempting to update the UI inside of onPostExecute(). This continues to add noise when you just want to get some work done in a clear, easily maintainable way.

Caching on rotation (or otherwise)

What if your user is staying on the same Activity, but just rotating the device? Canceling it doesn’t necessarily make sense in this case because you may end up having to start the task over again after rotation. Or you may not want to restart it because it mutates some state somewhere in a non-idempotent way, but you do want the result so you can update UI to reflect it.

If you are specifically doing a read-only load operation, you could use an AsyncTaskLoader to solve this problem. But in standard Android fashion, it still brings along with it a ton of boilerplate, lack of error handling, no caching across Activities, and more quirks of its own.

Composing multiple web service calls

Now let’s say we’ve managed to get all of that figured out and working ok, but we now need to make a few network calls back-to-back, each based on the result of the previous call. Or, we might want to make a few network calls in parallel to improve performance and then merge the results together before sending them back to the UI? Sorry to say that once again, AsyncTask will not help you here.

Once you start to do things like this, the previous issues start to pale in comparison to the complexity and pain that starts to grow with coordinating a more complex threading model. To chain calls together, you either keep them separate and end up in callback hell, or run them synchronously together in one background task end up duplicating work to compose them differently in other situations.

To run them in parallel, you will have to create a custom executor to pass around since AsyncTasks do not run in parallel by default. And to coordinate parallel threads, you’ll need to dip down into the more complex synchronization patterns using things like CountDownLatchs, Threads, Executors and Futures.

Testability

To top this all off, if you like to unit test your code, and I hope you do, AsyncTask will again not do you any favors. Testing an AsyncTask is difficult without doing something unnatural that’s most likely fragile and/or hard to maintain. Here’s a post talking about some ways to acheive it successfully.

Related: Find out why Ross believes frontend development teams should own APIs.

Better Solution: RxJava’s Observable

Luckily, all the issues we’ve discussed have an elegant solution once we’ve decided to pull in the RxAndroid library. Let’s see what it can do for us.

Now we will write the equivalent code of the AsyncTask we discussed above using Observables instead. (If you use Retrofit, and you should, then it supports the Observable return type and does its work in a background thread pool requiring no additional work on your part.)

webService.doSomething(someData)
          .observeOn(AndroidSchedulers.mainThread())
          .subscribe(
              result -> resultText.setText("It worked!"),
              e -> handleError(e));

Error handling

You’ll probably notice that, with no additional work, we are already handling both the success and error cases that the AsyncTask does not handle. And we’ve written a lot less code. The extra component you see there is declaring that we want the Observer to handle the results on Android’s main thread. This will change a bit as we go forward. And if your webService object doesn’t declare the work to run on a background thread, you could declare that here as well using .subscribeOn(...) (Note that these examples are assuming the use of Java 8’s lambda syntax. This can be achieved in Android projects using Retrolambda. It’s my current opinion that the reward is higher than the risk, and as of this writing we prefer to use it in our projects.)

Activity/Fragment lifecycle

Now, we can utilize Trello’s RxLifecycle library here to solve lifecycle challenges that we’ve mentioned above.  Doing so will look something like this:

webService.doSomething(someData)
          .compose(bindToLifecycle())
          .subscribe( result -> resultText.setText("It worked!"), e -> handleError(e));

bindToLifecycle() will hook your Observable chain into the Fragment lifecycle callback events and auto-unsubscribe causing execution to stop so that there’s no risk of running running your Fragment subscription code in an invalid state.  This also prevents leaking of Fragments and Activities since they are dereferenced during the unsubscription.

This takes care of the first two main issues. But the next one is when RxJava really starts to shine.

Composing multiple web service calls

I won’t go into great detail here because it is a deep topic, but by using Observables, you can do very complex things in a simple, easy-to-understand manner. Here’s an example of chaining web service calls that depend on each other, running the second batch of calls in parallel in a thread pool and then merging and sorting the results before sending them back to the Observer. I even threw a filter in there for good measure. All of this logic and coordination is declared in literally five lines of code. Let that sink in for a bit…


public Observable<List<Weather>> getWeatherForLargeUsCapitals() {
  return cityDirectory.getUsCapitals()
                      .flatMap(cityList -> Observable.from(cityList))
                      .filter(city -> city.getPopulation() > 500,000)
                      .flatMap(city -> weatherService.getCurrentWeather(city)) //each runs in parallel
                      .toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()));
}

Caching on rotation (or otherwise)

Now since this is a “load” of data, we probably want to cache it so that just rotating the device doesn’t trigger the whole set of web service calls again. One way to accomplish this is to set the Fragment to be retained and store a reference to a cache of the Observable like so:

@Override
public void onCreate(Bundle savedInstanceState) {
  super.onCreate(savedInstanceState);
  setRetainInstance(true);
  weatherObservable = weatherManager.getWeatherForLargeUsCapitals().cache();
}

public void onViewCreated(...) {
  super.onViewCreated(...)
  bind(weatherObservable).subscribe(this);
}

After rotation, subscribing to the cache instance will immediately emit the same items as the first time and prevent it from actually going to the web service again.

If you want to avoid retaining the Fragment (or you can’t because it’s a child fragment) we could also accomplish caching by putting the same cache instance one layer down inside a service singleton, by using an AsyncSubject which will re-emit the last item whenever it’s subscribed to, or using a BehaviorSubject to get the last value and also new values as things change throughout the application. (I’ll talk more about this in a later post where we use ‘hot’ Observables in a more event bus style.

## WeatherListFragment.java ##
public void onViewCreated() {
  super.onViewCreated()
  weatherManager.getWeatherForLargeUsCapitals()
                .compose(bindToLifecycle())
                .observeOn(mainThread())
                .subscribe(this);
}
## WeatherManager.java ##
public Observable<List<Weather>> getWeatherForLargeUsCapitals() {
    if (weatherCache == null) {
        weatherCache = cityDirectory.getUsCapitals()
                                    .flatMap(cityList -> Observable.from(cityList))
                                    .filter(city -> city.getPopulation() > 500,000)
                                    .flatMap(city -> weatherService.getCurrentWeather(city))
                                    .toSortedList((cw1,cw2) -> cw1.getCityName().compare(cw2.getCityName()))
                                    .cache();
    }

    return weatherCache;
}

Since the “cache” is managed by the Manager singleton, it’s not tied to the Fragment/Activity lifecycle and will persist beyond this Fragment/Activity. If you want to force refreshes of the cache based on lifecycle events in a similar way to the retained fragment example, you could do something like this:

public void onCreate() {
  super.onCreate()
  if (savedInstanceState == null) {
    weatherManager.invalidate(); //invalidate cache on fresh start
  }
}

The great thing about this is, unlike Loaders, we have the flexibility to cache this result across many Activities and Services if we choose. Just remove the invalidate() call in onCreate() and let your Manager object decide when to emit new weather data. Possibly on a timer, possibly when the user changes location, possibly ____. It really doesn’t matter. You now have control over when and how to invalidate the cache and reload. And the interface between your Fragments and your Manager objects doesn’t change when your caching strategy changes. It’s always just an Observer of a List<WeatherData>.

Testability

Testing is the final piece of the puzzle we want to ensure is clean and simple. (Let’s ignore the fact that we probably want to mock out the actual web services during this test. Doing this is as simple as following the standard pattern of injecting those dependencies via an interface as you may already be doing.)

Luckily, Observables give us a simple way to turn an async method into a synchronous one. All you have to do is use the .toBlocking() method. Let’s look at an example test for our method above.

List results = getWeatherForLargeUsCapitals().toBlocking().first();
assertEquals(12, results.size());

That’s it. We don’t have to do fragile things like sleeping the thread or complicate our test by using Futures or CountDownLatchs. Our test is now simple, clear and maintainable.

Conclusion

Here are some sample projects that demonstrate using Observables for AsyncTask style work and also AsyncTaskLoader style work.

There you have it. We just learned how to replace AsyncTask and AsyncTaskLoader with rx.Observable interface while achieving both more power and more clarity in the code that we write. Happy Rx-ing and I look forward to presenting more solutions to common Android problems using RxJava Observables.

0 replies

Leave a Reply

Want to join the discussion?
Feel free to contribute!

Leave a Reply

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