How Kotlin coroutines are better than RxKotlin?
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:
Coroutines are better to deal with resources
- In RxJava you can assign computations to schedulers but
subscribeOn()
andObserveOn()
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. - Coroutines give more control on when those computation occur. You can for example pass hand (
yield
), prioritize (select
), parallelize (multipleproducer
/actor
onchannel
) 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. - Due to it's reactive nature, backpressure doesn't fit well in RxJava. In the other end
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 alsooffer()
to channel, in which case the call never suspend but returnfalse
in case the channel is full, effectively reproducingonBackpressureDrop()
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:
Coroutines are better at removing callback from your code
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.
Kotlin coroutines are different from Rx. It is hard to compare them apples-to-apples, because Kotlin coroutines are a thin language feature (with just a couple of basic concepts and a few basic functions to manipulate them), while Rx is a pretty heavy library with quite large variety of ready-to-use operators. Both are designed to address a problem of asynchronous programming, however their approach to solution is very different:
Rx comes with a particular functional style of programming that can be implemented in virtually any programming language without support from the language itself. It works well when the problem at hand easily decomposes into a sequence of standard operators and not so well otherwise.
Kotlin coroutines provide a language feature that let library writers implement various asynchronous programming styles, including, but not limited to functional reactive style (Rx). With Kotlin coroutines you can also write your asynchronous code in imperative style, in promise/futures-based style, in actor-style, etc.
It is more appropriate to compare Rx with some specific libraries that are implemented based on Kotlin coroutines.
Take kotlinx.coroutines library as one example. This library provides a set of primitives like async/await
and channels that are typically baked into other programming languages. It also has support for light-weight future-less actors. You can read more in the Guide to kotlinx.coroutines by example.
Channels provided by kotlinx.coroutines
can replace or augment Rx in certain use-cases. There is a separate Guide to reactive streams with coroutines that goes deeper into similarities and differences with Rx.