Why would I want to use Kotlin's coroutines?
It seems that the RxKotlin library is much more versatile. Kotlin's coroutines look significantly less powerful and more cumbersome to use in comparison.
I base my opinion on coroutines on this design talk by Andrey Breslav (JetBrains)
Slideshow from the talk is accessible here.
EDIT (thanks to @hotkey):
Better source on the current state of coroutines here.
Disclaimer: Parts of this answer are irrelevant since Coroutines now have the flow API, very similar to Rx one. If you want an up-to-date answer, jump to the last edit.
There is two parts in Rx; the Observable pattern, and a solid set of operators to manipulate, transform and combine them. The Observable pattern, by itself, doesn't do a lot. Same with Coroutines; it's just another paradigm to deal with asynchronism. You can compare the pro/cons of callbacks, Observable and coroutines to solve a given problem, but you can't compare a paradigm with a fully featured library. It's like comparing a language with a framework.
How Kotlin coroutines are better than RxKotlin ? Didn't used coroutines yet, but it's look similar to async/wait in C#. You just write sequential code, everything is as easy as writing synchronous code ... except it execute asynchronously. It's easier to grasp.
Why would I want to use kotlin coroutines ? I will answer for myself. Most of the time I will stick to Rx, because I favor event-driven architecture. But should arise the situation where I am writing sequential code, and I need to call an asynchronous method in the middle, I will happily leverage coroutines to keep it that way and avoiding wrapping everything in Observable.
Edit: Now that I am using coroutines it's time for an update.
RxKotlin is just syntactic sugar to use RxJava in Kotlin, so I will speak about RxJava and not RxKotlin in the following. Coroutines are a lower lever and more general concept than RxJava, they serve others use-cases. That said, there is one use-case where you could compare RxJava and coroutines (channel
), it's passing around data asynchronously. Coroutines have a clear advantage over RxJava here:
subscribeOn()
and ObserveOn()
are confusing. Every coroutine is given a thread context and return to parent context. For a channel, both side (producer, consumer) execute on his own context. Coroutines are more intuitive on thread or thread pool affectation.yield
), prioritize (select
), parallelize (multiple producer
/actor
on channel
) or lock resource (Mutex
) for a given computation. It may not matter on server (where RxJava came first) but on resources limited environment this level of control may be required.send()
to channel is a suspensive function that suspend when channel capacity is reached. It's out-of-the-box backpressure given by nature. You could also offer()
to channel, in which case the call never suspend but return false
in case the channel is full, effectively reproducing onBackpressureDrop()
from RxJava. Or you could just write your own custom backpressure logic, which won't be difficult with coroutines, especially compared to do the same with RxJava.There is another use-case, where coroutines shine and this will answer your second question "Why would I want to use Kotlin coroutines?". Coroutines are the perfect replacement for background threads or AsyncTask
(Android). It's as easy as launch { someBlockingFunction() }
. Of course you could achieve this with RxJava too, using Schedulers
and Completable
perhaps. You won't (or little) use the Observer pattern and the operators which are the signature of RxJava, a hint that this work is out of scope for RxJava. RxJava complexity (a useless tax here) will make your code more verbose and less clean than Coroutine's version.
Readability matters. On this regard, RxJava and coroutines approach differ a lot. Coroutines are simpler than RxJava. If you are not at ease with map()
, flatmap()
and functional reactive programming in general, coroutines manipulations are easier, involving basics instructions: for
, if
, try/catch
... But I personally find coroutine's code harder to understand for non-trivial tasks. Especially it involves more nesting and indentation whereas operator chaining in RxJava keep everything in line. Functional-style programming make processing more explicit. On top of that RxJava can solve complex transformations with a few standard operators from their rich (OK, way too rich) operator set. RxJava shine when you have complex data flows requiring a lot of combinations and transformations.
I hope those considerations will help you choose the right tool given your needs.
Edit: Coroutine now have flow, an API very, very similar to Rx. One could compare pro/cons of each, but the truth is the differences are minor.
Coroutines as it's core is a concurrency design pattern, with add-on libraries, one of those being a stream API similar to Rx. Obviously, Coroutines having a far broader scope than Rx, there is a lot of things that Coroutines can that Rx can't, and I can't list them all. But usually if I use Coroutines in one of my project it boil down to one reason:
I avoid using callback wich harm readability too much. Coroutines make asynchronous code simple and easy to write. By leveraging the suspend keyword, your code look like synchronous one.
I have seen Rx used in project mostly for the same purpose of replacing callback, but if you don't plan to modify your architecture to commit to the reactive pattern, Rx will be a burden. Consider this interface:
interface Foo {
fun bar(callback: Callback)
}
The Coroutine equivalent is more explicit, with a return type and the keyword suspend indicating it's an asynchronous operation.
interface Foo {
suspend fun bar: Result
}
But there is a problem with the Rx equivalent:
interface Foo {
fun bar: Single<Result>
}
When you call bar() in the callback or Coroutine version, you trigger the computation; with the Rx version, you get a representation of a computation that you can trigger at will. You need to call bar() then subscribing to the Single. Usually not a big deal, but it's a little confusing for beginner and can lead to subtle problem.
One exemple of such problems, suppose the callback bar function is implemented as such:
fun bar(callback: Callback) {
setCallback(callback)
refreshData()
}
If you don't port it properly, you will end with a Single that can be triggered only once because refreshData() is called in bar() function and not at subscription time. A beginner mistake, granted, but the thing is Rx is way more than a callback replacement and a lot of developers struggle to grasp Rx.
If your objective is to transform an asynchronous task from callback to a nicer paradigm, Coroutines are a perfect fit whereas Rx add some complexity.