Skip to content

Coroutines vs RxJava: The Way Forward with Asynchronous Programming for Android

So Google I/O 2019 just passed a couple weeks ago and to no surprise, in one of their very many talks, they brought up Kotlin’s new asynchronous feature: Coroutines.

Coroutines in a nutshell for those who don’t know: They workaround the use of callbacks by making them into more sequential code through the use of Suspend Functions for more concurrent code that can still switch through threads easily.

suspend fun loadUser() {
    val user = api.fetchUser()
    show(user)
}

It’s integrated directly into Kotlin and while it is very new, there are already libraries that support it including Retrofit.

If this is your first time hearing about Coroutines, I recommend you either watch the video above or see my post on Coroutine Basics.

RxJava on the other hand works around callbacks in a different way through the use of Observable Streams. It revolves around 2 core components, Observables and Observers. Observables are streams that are ready to fetch data from an API or somewhere and emit it, while the Observer receives that data after telling the Observable to start emitting by “subscribing” to it.

fun getUser(): Observable<User> {
    return api.fetchUser()
}

fun loadUser() {
    val userObserver = getUser()
        .observeOn(Schedulers.io())
        .subscribe { user -> show(user) }
}

RxJava is already quite well established prompting many employers to specifically look for developers who are adept in the use of this library, however, will Coroutines take its place?

Let’s start by looking at the similarities, pros, and cons of each and tally each one up to see which one is the way forward for asynchronous programming.

Coroutines and RxJava: The Similarities

1. Both get rid of callback hell

This is their main purpose, why they were created. Callbacks are very inconcurrent, and definitely not concise. Thank god for lambda functions because before those, they just bloat your code massively. They still kinda do.

Worst is callbacks leak everywhere. Without code to say “Hey I don’t want this callback anymore”, your callbacks will continue to run when they’re not needed and your memory is just gonna go boom. Exception.

And the code that does stop the callbacks?

override fun onStop() {
    subscription1.cancel()
    subscription2.cancel()
    subscription3.cancel()
    subscription4.cancel()
    subscription5.cancel()
    subscription6.cancel()
}

Yeah…. Nah.

2. Both can switch between threads pretty well

suspend fun fetchUser() {
    withContext(Dispatchers.IO) {
        /* Network Call */
    }
}

Thread switching is important. Network calls can only be done in background threads, while UI changes can only be done on the main thread. Ideally, you’d want to make UI updates in response to network calls.

Both Coroutines and RxJava offer switching between the Main Thread, Computation, and IO. Coroutines however have the Unconfined thread option which can switch threads in the middle of a coroutine, while RxJava has a few more options including Trampoline, Single, and NewThread.

3. Both have strong error-handling methods

val observer = myObservable
              .retry(3)
              .onErrorResumeNext(Observable.just(2, 4, 6))
              .subscribe()

Coroutines have built in exception handling that propagates through parent and children. Since Coroutines follow a more hierarchical structure, you can manipulate which parts of the Coroutine to cancel or continue.

RxJava on the other hand provides different ways to handle exceptions from retries to emitting a default piece of data on error to just executing a whole different method, and they make this easy to set up uniquely for each stage of the emission process.

Coroutines: The Simple but Compact

Pro #1. They’re Simple

suspend fun loadUser() {
    val user = api.fetchUser()
    show(user)
}
fun getUser(): Observable<User> {
    return api.fetchUser()
}

fun loadUser() {
    val userObserver = getUser()
        .observeOn(Schedulers.io())
        .subscribe { user -> show(user) }
}

Let’s go back to the code snippets used above. Which is sexier?

This is the main thing coroutines have going on for them. It’s easy to learn and it’s easy to read. There are very few concepts you need to learn about which don’t go much further than Suspend Functions and Scopes. Code can be written in a sequential manner and it very much feels like it’s a native part of the Kotlin language… because it kind of is.

How is this a major advantage then? Well it’s easier to learn so new developers can take advantage of it. Other libraries like Retrofit can more easily integrate it into their own systems which benefits us in the end, and because it’s easier to read as well, the app becomes easier to maintain whether you’re working on it yourself or with a team.

Pro #2. Main-Safety

suspend fun fetchDocs() {                      // Dispatchers.Main
    val result = get("developer.android.com")  // Dispatchers.Main
    show(result)                               // Dispatchers.Main
}

suspend fun get(url: String) =                 // Dispatchers.Main
    withContext(Dispatchers.IO) {              // Dispatchers.IO (main-safety block)
        /* perform network IO here */          // Dispatchers.IO (main-safety block)
    }                                          // Dispatchers.Main
}

This is code I’ve taken from the developer documentation on Coroutines. These suspend functions run primarily on the main thread, but the network call part of the function is wrapped in a withContext block where it can execute on the IO thread.

The overhead on this is close to none especially when compared to traditional callbacks, and if a function makes multiple network calls, you can simply wrap it all in a single outer withContext so all inner withContext blocks don’t go through the expense of switching to the IO thread because they’re already on it.

Pro #3: Lightweight and Efficient

Coroutines at their core are just lightweight threads that can communicate with each other and cost almost nothing in terms of performance. Not to say RxJava will slow your app down to a crawl, but when comparing the overhead of the two, Coroutines are just lighter.

Another thing is that if you choose to completely omit RxJava from your app in favor of Coroutines, that’s one less dependency in your app resulting in a slimmer storage size.

The Big Con: That’s Pretty Much It

There’s quite a lot packed into Coroutines, don’t get me wrong. But when creating this paradigm, the developers clearly favored simplicity over power. This isn’t inherently a bad thing, but it does mean it falls short of some niche cases where a bit more functionality could’ve come in handy.

RxJava: The Powerful but Bulky

Pro #1: Rich Set of Operators

val observable = Observable.range(1, 10)
    .map{ n -> n / 2 }
    .observeOn(Schedulers.io)
    .subscribe()

One of the biggest things RxJava has going on for it is its huge set of operators that can be used for all kinds of situations from combining data streams to transforming or even applying mathematical shinenigans to them. So many that it took me 6 whole weeks to cover them all (don’t judge, student life is now harder than ever).

While Coroutines can make use of the many operations accessible to Kotlin Collections, it still just pales in comparison with RxJava’s insane set.

Pro #2: Hot Data Streams

// Converting a cold observable to a hot one
ConnectableObservable<Integer> hot = cold.publish();
hot.connect();

RxJava can make use of what’s called a Hot Observable, a data stream that goes through its emitting process whether or not it has any subscribers listening to it. Like a mailing list that continues to send out emails, but you as a subscriber will only receive emails from the moment you subscribe and not get every single previous email. This is great if we don’t want to emit any outdated data.

Coroutines may or may not have an implementation of this, but as far as I know, there isn’t a simple one. Coroutines may be sexy, but RxJava sure is hotter.

Pro #3: Works Better in Real-time

RxJava’s Observables can listen for broadcasts of new data without manually having to check themselves. This means they only execute when necessary and have numerous different ways to queue data streams waiting to be emitted from a standard FIFO emission style, to emitting only the latest piece of data that came in.

For quick successive updates happening every few milliseconds, Coroutines could handle them fine if it were all offline. When things go online, Coroutines start to lose efficiency and RxJava starts taking the lead.

The Big Con: It’s Quite Convoluted

Anyone’s first time seeing a class written with RxJava will be thinking “what the hell is all this”. It’s not very simple to the naked eye and has a slightly steep learning curve thanks to all its unique concepts from Observables, it’s massive set of operators, and how different stages of a single data emission process can be scattered all over the place.

As the counter argument to Coroutine’s simplicity goes, code can go wrong pretty quickly and before you know it, you’ve created a monster in your code and it becomes impossible to maintain. It takes a bit to learn it, it takes quite a bit more to become good enough at it that it becomes practical.

So Who Wins?

Well… It’s a case-to-case scenario really.

If your app…

  • is already running with RxJava, don’t bother switching
  • runs network calls once or twice per activity creation, use Coroutines
  • needs to be simple and easily-read by less experienced coders, Coroutines
  • runs any sort of real-time feature, use RxJava
  • needs high levels of data manipulation between fetching and emitting, RxJava
  • needs a combination of the use cases above, well combine the two!

While rules like these can be set, these are just my opinion and every developer has their own style of coding because as much as it’s a science, it’s also an art. Take it your way. If you have an undeniable hate for one or the other, you’re free to just completely discard it. Why do you think I never talk about LiveData? ʕ•ᴥ•ʔ