Streamlining Asynchronous Programming on Android

October 02, 2023 , , , ,

Background

Asynchronous programming plays a vital role in mobile development, ensuring that applications remain responsive to user input even while fetching data from databases or networks. At Wealthfront, we heavily rely on asynchronous programming for making network calls to the Wealthfront API server on Android. Initially, RxJava was the go-to choice for this purpose, serving as the de facto method. However, with the introduction of Kotlin coroutines, it emerged as a superior alternative and prompted us to migrate our code base. In this article, I will guide you through our journey of transitioning from RxJava to Kotlin coroutines, highlighting the various decisions we made along the way.

RxJava

RxJava was what Wealthfront used to use for asynchronous programming on Android before Coroutine. It solves the problems of unmanageable nested callbacks posed by AsyncTask in the standard Android SDK. By offering a rich set of operators, RxJava empowers developers to transform and combine multiple asynchronous tasks in a flexible manner. To provide more context, let’s delve into the specific limitations faced by AsyncTask and how RxJava overcomes them.

Below is an example of a single API call using RxJava. The response object is an RxJava Observable and can be combined with other observables using RxJava operators to form complex asynchronous logic.

RxJava’s syntax is much cleaner than AsyncTask, and it allows the caller to be notified when the asynchronous logic starts, terminates, and to decide which thread each callback method is running on. However, it can become more complicated when dealing with multiple API calls. In that case, developers must learn more about RxJava operators and apply them. Here is another example of making two parallel API calls and waiting for the latest result being returned from both endpoints using RxJava with the combineLatest operator:

Although RxJava makes async programming easier than using Java’s thread interface or Android SDK’s AsyncTask, there is a steep learning curve for beginners. Understanding how operators work and writing code that behaves as intended can be challenging. In some use cases, figuring out which one executes first can be important and cause bugs if not done correctly. For example, understanding the execution order of doOnTerminate() and the code block inside subscribe() requires knowing some of the internals about how RxJava works.

Migration to Coroutine

As the Android development community shifted from Java to Kotlin, the need arose for an improved solution to address the limitations of RxJava. Fortunately, Kotlin introduced Coroutines as an experimental feature for asynchronous programming, and it was steadily maturing during our transition away from RxJava at Wealthfront. Coroutines proved to be a native solution for Kotlin, effectively tackling many of the drawbacks associated with RxJava. Let’s explore how Coroutines address these limitations and provide a more seamless and efficient programming experience.

Kotlin coroutines take a distinct approach in addressing the drawbacks of RxJava. Rather than providing an extensive set of operators, coroutines enable asynchronous logic to be written in a more synchronous manner, eliminating the need for numerous operators to combine asynchronous code. While this concept may not be new to other programming languages like JavaScript or C#, it represents a fresh paradigm in Android development. By embracing coroutines, developers can enjoy a more intuitive and streamlined approach to handling asynchronous tasks, resulting in cleaner and more readable code.

Another advantage of coroutine is that hundreds of coroutines can be run on the same thread which makes it very efficient to handle IO related asynchronous code.

Wealthfront app uses restful API to communicate with servers. As you can see, our existing API is defined as a collection of methods which returns an RxJava object and will be irrelevant in the coroutine world. In order to use coroutine for networks calls, the first step is to define a new API class which can be called from coroutine, We give it the name “Suspend API” because the api definition is essentially a coroutine suspend function that can be used in a coroutine scope.

Another ingredient we added to the new suspend API is we redefined the generic response object so that all the response types (success, 400, 500, etc)  can be handled through visitor pattern. In RxJava,  the error is being thrown from the API and needs to be handled as an exception in a separate code block. Sometimes it’s not straightforward to figure out the exact moment that block is being run without decent RxJava knowledge. 

To summarize, this enables us to do two things:

  1. Write asynchronous code in a synchronous fashion
  2. Use visitor patterns (when.exhaustive) to enforce handling all network response types.

Let’s see an example of how suspend API is being used to make a single network call.

Instead of having a hard time to figure out which line executed first in RxJava, It’s intuitive to see  the first line is always executed first and second line executed after the first and so forth. 

It’s also very intuitive to combine parallel API calls without the need to look up a thick operator menu like RxJava.

In terms of implementation, at Wealthfront, we use Dagger for dependency injection, Retrofit for API response transformation and OkHTTP for network calls. To make the newly defined API work for coroutine, we defined some custom modules and adapters so that retrofit could transform the response from network to ApiResult we defined. The ApiResult can later be used by coroutine as  a suspend function.

Wealthfront's co-routine API implementation

At Wealthfront, we heavily rely on tests for quality assurance. We added some extensions methods so that coroutine can be tested properly by Mockito framework. As an example, coWhenever and coVerify is the counterpart of When and Verify in Mockito for coroutines.

Conclusion

I hope this blog post helps you better understand how we do asynchronous programming on Android at Wealthfront and may provide some ideas for your own project. The introduction of coroutine and suspend API  makes it much easier to read, write and reason about asynchronous logic. Since it was introduced, we have migrated most of our existing APIs to suspend API and coroutines. All new apis are written in coroutine style too. We are also actively exploring more scenarios making use of coroutines. If this sounds interesting and you would like to help the team to build better Android apps using the most recent technologies, check out our careers page!

Disclosures

The information contained in this communication is provided for general informational purposes only, and should not be construed as investment or tax advice. Nothing in this communication should be construed as a solicitation or offer, or recommendation, to buy or sell any security. Any links provided to other server sites are offered as a matter of convenience and are not intended to imply that Wealthfront Advisers or its affiliates endorses, sponsors, promotes and/or is affiliated with the owners of or participants in those sites, or endorses any information contained on those sites, unless expressly stated otherwise. All investing involves risk, including the possible loss of money you invest, and past performance does not guarantee future performance. Please see our Full Disclosure for important details. Wealthfront offers a free software-based financial advice engine that delivers automated financial planning tools to help users achieve better outcomes. Investment management and advisory services are provided by Wealthfront Advisers LLC, an SEC registered investment adviser, and brokerage related products are provided by Wealthfront Brokerage LLC, a member of FINRA/SIPC. Wealthfront, Wealthfront Advisers and Wealthfront Brokerage are wholly owned subsidiaries of Wealthfront Corporation.

© 2023 Wealthfront Corporation. All rights reserved.