Coroutines and RxJava — An Asynchronicity Comparison (Part 5): Operators

Introduction

In this blog series I will compare Kotlin Coroutines and RxJava since they are both trying to solve a common problem in Android development: Asynchronous Programming.

In Part 3 and Part 4 we talked about transferring stream of values and the Coroutines and RxJava interop library.

Now it’s time to compare how we can transform those streams with Operators.

Common Operators

Some RxJava operators are available in the Kotlin standard library as part of Kotlin Collections. Here you can see a table comparing simple operators.

Simple operators in RxJava and Coroutines

These operators transform the stream in the same way even though some operators differ in name: e.g. skip in RxJava is called drop in Coroutines.

Create Your Own Operator

Some operators are not part of Kotlin Collections. However, you can create them yourself with little effort.

We can replicate what the range RxJava operator does with few lines of code and a simple for loop.

fun range(
context: CoroutineContext,
start: Int,
count: Int
) = publish(context) {
for (x in start until start + count) send(x)
}

Some other operators require more work. In the example below you can see the implementation of the Completable.zip RxJava operator which takes two blocks of code and waits for both of them to finish.

suspend fun zip(
context: CoroutineContext,
block: () -> Unit,
block2: () -> Unit
) {
val deferred1 = async(context) { block() }
val deferred2 = async(context) { block2() }
    deferred1.await()
deferred2.await()
}
If you noticed, we pass a CoroutineContext as a parameter. We do that so we can cancel the operator easily by calling .cancel() on that context’s Job as we talked about in Part 2.

Complex Operators

What about even more complex RxJava operators like debounce?

Complex operators in RxJava and Coroutines

You can find debounce as an extension function on ReceiveChannel. RxJava timeout has an equivalent in Kotlin Coroutines with withTimeoutOrNull, etc.

Similarities and Differences

We see that most operators are available in both libraries and if not, you can easily build them.

The only difference I can see in these two libraries is at which point you apply those operators

Whereas in RxJava you can apply operators before subscribing to the stream, you have to do it after opening a subscription in Coroutines. Let’s see how we map an element in RxJava:

rxObservable
.map { result -> map(result) }
.subscribe({
consumeResult(it)
})

And now how we do it in Coroutines:

broadcastChannel
.openSubscription()
.map { result -> map(result) }

In Coroutines we have to do it after opening a subscription because map in Coroutines is an extension function on ReceiveChannel<E>. This is the case for other operators such as filter, drop, etc. Calling openSubscription() returns a SubscriptionReceiveChannel<T> object which extends ReceiveChannel<E>.

In my opinion, Coroutines requires extra work if you want multiple Observers to apply the same operators. Of course you can do it! But it requires more code.

What’s Coming Next?

Threading is the topic for the sixth part of this series.

If you want to know how you can swap threading in Coroutines the same way you do it in RxJava with subscribeOn() and observeOn(), don’t miss next week’s blog post!