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

54 篇文章 0 订阅

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

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 to use it when solving concrete problems in their Android projects. In this series of posts, 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.

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, AsyncTaskLoaderhas 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.

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 ofonPostExecute(). 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 sinceAsyncTasks 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.

Better Solution: RxJava’s Observable

Luckily, all the issues we’ve discussed have an elegant solution once we’ve decided to pull in theRxAndroid 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.)

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 will want to utilize RxAndroid here to solve the lifecycle problems that we’ve mentioned above and get rid of the need to specify the mainThread() scheduler. (You only need to import RxAndroid btw). Doing so will look like this:

What I typically do is create a helper method in my application base Fragment to simplify this a bit. You can see the base RxFragment I’ve been maintaining here to give you an idea.

AppObservable.bindFragment() inserts a shim into the call chain that prevents onNext() calls from running if our Fragment/Activity is no longer in a valid state. If it is in an invalid state when it tries to run, the subscription will get unsubscribed and execution will stop so that there’s no risk of a NPE and the resulting crash. One thing to note here is that when we leave this Fragment/Activity, we will leak it either temporarily or permanently, depending on the behavior of the Observable in question. So inside the bind() method, I will also hook into theLifeCycleObservable mechanism to have it auto-unsubscribe when the Fragment is destroyed. The good thing is you can take care of this once and never really think about it again.

So, 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…

Caching on rotation (or otherwise)

Now since this is a “load” of data, we probably want to cache this data 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:

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.

If you want to avoid retaining the Fragment (and there are good reasons to avoid it) we could also accomplish caching by making use of an AsyncSubject which will re-emit the last item whenever it’s subscribed to. Or we could use 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.

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:

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.

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

There you have it. We just replaced the functionality of both AsyncTask and AsyncTaskLoader using the 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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值