February 21, 2020

Forget RxJava: Kotlin Coroutines are all you need. Part 1/2

Forget RxJava: Kotlin Coroutines are all you need. Part 1/2

Hi!

RxJava is an amazing technology which gave us completely different developer experience on Android apps several years ago, permitting to drop infinite AsyncTasks, Loaders and other tools replacing it with concise functional style code.

For example, a short interface of network layer in an application working with GitHub API created with RxJava can look like this:

Although RxJava is a powerful library, it is not meant to be used as a tool to manage async work. It is an event processing library.

It is already common we use Single to represent a result of a network operation, which can be resolved to some value or fail.

The code in your activity/fragment which uses this interface can look like this(tests for it are considered later):

Looking more or less understandable, this code however has several implicit pitfalls:

Performance overhead

Each line here generates an internal object(or several ones) to do the job. For this particular case it has 19 objects generated. Imagine the number when you have more complex case.

Memory dump part for io.reactivex package

Unreadable stacktrace

Imagine you made an error in code or haven’t thought about some corner case. That case is left uncovered during QA and successfully went into production. Now a stack-trace comes from your crash reporting tool:

at com.epam.talks.github.model.ApiClientRx$ApiClientRxImpl$login$1.call(ApiClientRx.kt:16)
at io.reactivex.internal.operators.single.SingleFromCallable.subscribeActual(SingleFromCallable.java:44)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleFlatMap.subscribeActual(SingleFlatMap.java:36)
at io.reactivex.Single.subscribe(Single.java:3096)
at io.reactivex.internal.operators.single.SingleMap.subscribeActual(SingleMap.java:34)
at io.reactivex.Single.subscribe(Single.java:3096)at io.reactivex.internal.operators.single.SingleSubscribeOn$SubscribeOnObserver.run(SingleSubscribeOn.java:89)at io.reactivex.Scheduler$DisposeTask.run(Scheduler.java:463)at io.reactivex.internal.schedulers.ScheduledRunnable.run(ScheduledRunnable.java:66)at io.reactivex.internal.schedulers.ScheduledRunnable.call(ScheduledRunnable.java:57)at java.util.concurrent.FutureTask.run(FutureTask.java:266)at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:301)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1162)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:636)at java.lang.Thread.run(Thread.java:764)

Cool, 1 line of the whole stacktrace is from your actual code!

The learning complexity

Remember how much time you spent understanding the difference between map() and flatMap() ? What about thousands of other operators? This time is required for every new developer coming to a world of reactive programming.

Readability

The code is readable, however we still pass callbacks which doesn’t represent our mind model (we think sequentially).


What if Kotlin Coroutines can make our life better?

First of all let’s find out if we have any replacement for Single object. In coroutines world the appropriate object would be a Deferred interface. It looks like this:

So, applying a simple refactoring of our ApiClient interface would result in that code:

Implementation will change accordingly: we need to replace Single.fromCallable with some coroutine builder. async is good here:

As you may know RxJava will require you to choose the Scheduler for your async code; in Coroutines code the similar entity is called Dispatcher. By default async and launch coroutine builders use CommonPool dispatcher, however you can always pass any other. Ok, let’s see how our client code changed:

Wow! The code became so much clearer and straightforward! Looks like nothing asynchronous actually happens :) By the way, in RxJava version we were adding our subsribtion to a compositeDisposable in order to dispose() it in onStop(). Here we are saving job to call job.cancel() in the same place. Stay tuned for more about lifecycle and coroutines in the upcoming articles!

But what about the minuses we found for the RxJava code? Have they gone for good?

Performance

The number of objects under the hood went down to 11(or by a third).

Unreadable stack trace

The stacktrace is still unrelated but this issue is being addressed.

Readability

The code is easier to read and write because the async code is written the way as it was synchronous.


How do I refactor the tests?

With RxJava we had the following test:

I am using KHttp and mockk here.

With Kotlin Coroutines the test becomes the following:

The test hasn’t changed a lot — we removed subscribe call and added a use of runBlocking coroutine builder — so that our test doesn’t finish before the code under the test is not yet completed.

Do you have any further improvements?

Surely I do. Instead of returning a Deferred we may get rid of any wrappers for our business objects and pretend nothing actually happens asynchronously. To do that we replace a Deferred object with a suspend modifier:

Wow! Do you like how clean our interface became? I do a lot. Let’s look how out client code and tests are changed:

It looks like only

async(parent = job) {}

calls are added. The rest is the same.

Passing the parent is required in order for this coroutines to be cancelled when job is cancelled in onStop().

Moreover, we can test our presenter in a fancy way:

At line 7 we are mocking our function with a suspend modifier to return a business object immediately.

For those using Mockito the code to mock a suspend function is here:

given {  
  runBlocking {    
    apiClient.login(any())  
  }
}.willReturn (githubUser)

A little bit uglier comparing to mockk, but still working. runBlocking here is a coroutine builder which blocks the thread the coroutine is running in. See more here.

Summary

Ok, we managed to refactor some interface working with Singles to use kotlin coroutines and benefited from it. In the next part we will consider what means coroutines provide to handle more advanced topics of RxJava.


If you liked the article, come say hi on twitter. Further announcements and general thoughts about Kotlin/Android are made there.