RxJava to Coroutines: end-to-end feature migration

Vadims Savjolovs
Jan 13 · 5 min read

Kotlin coroutines are much more than just lightweight threads — they are a new paradigm that helps developers to deal with concurrency in a structured and idiomatic way.

When developing an Android app one should consider many different things: taking long-running operations off the UI thread, handling lifecycle events, cancelling subscriptions, switching back to the UI thread to update the user interface. In the last couple of years RxJava became one of the most commonly used frameworks to solve this set of problems. In this article I’m going to guide you through the end-to-end feature migration from RxJava to coroutines.

Feature

The feature we are going to convert to coroutines is fairly simple: when user submits a country we make an API call to check if the country is eligible for a business details lookup via a provider like Companies House. If the call was successful we show the response, if not — the error message.

Migration

Architecture

We are going to migrate our code in a bottom-up approach starting with Retrofit service, moving up to a Repository layer, then to an Interactor layer and finally to a ViewModel.

Functions that currently return should become suspending functions and functions that return should return . In this particular example we are not going to do anything with Flows.

Retrofit Service

Let’s jump straight into the code and refactor the method in to coroutines. This is how it looks like now.

BusinessLookupService RxJava

Refactoring steps:

  1. Starting with version 2.6.0 Retrofit supports the modifier. Let’s turn the method into a suspending function.
  2. Remove the wrapper from the return type.
BusinessLookupService coroutines

is a sealed class that represents or . is constructed in a custom Retrofit call adapter. In this way we restrict data flow to only two possible cases — success or error, so consumers of don’t need to worry about exception handling.

Repository

Let’s move on and see what we have in . In the method body we call (the one we have just refactored) and use RxJava’s operator to transform to a and map response model to domain model. is another sealed class that represents and contains the object in case if the network call was successful. If there was an error in the network call, deserialization exception or something else went wrong we construct with a meaningful error message ( is typealias for String).

BusinessLookupRepository RxJava

Refactoring steps:

  1. becomes a function.
  2. Remove the wrapper from the return type.
  3. Methods in the repository are usually performing long-running tasks such as network calls or db queries. It is a responsibility of the repository to specify on which thread this work should be done. By we are telling RxJava that work should be done on the thread. How could the same be achieved with coroutines? We are going to use with a specific dispatcher to shift execution of the block to the different thread and back to the original dispatcher when the execution completes. It’s a good practice to make sure that a function is main-safe by using . Consumers of shouldn’t think about which thread they should use to execute the method, it should be safe to call it from the main thread.
  4. We don’t need the operator anymore as we can use the result of in a body of a function.
BusinessLookupRepository coroutines

Interactor

In this specific example doesn’t contain any additional logic and serves as a proxy to . We use invoke operator overloading so the interactor could be invoked as a function.

BusinessLookupEligibilityInteractor RxJava

Refactoring steps:

  1. becomes .
  2. Remove the wrapper from the return type.
BusinessLookupEligibilityInteractor coroutines

ViewModel

In we call that returns . We subscribe to the stream and observe it on the UI thread by specifying the UI scheduler. In case of we assign the value from a domain model to a LiveData. In case of we assign an error message.

We add every subscription to a and dispose them in the method of a ViewModel’s lifecycle.

BusinessProfileViewModel RxJava

Refactoring steps:

  1. In the beginning of the article I’ve mentioned one of the main advantages of coroutines — structured concurrency. And this is where it comes into play. Every coroutine has a scope. The scope has control over a coroutine via its job. If a job is cancelled then all the coroutines in the corresponding scope will be cancelled as well. You are free to create your own scopes, but in this case we are going leverage the lifecycle-aware . We will start a new coroutine in a using . The coroutine will be launched in the main thread as has a default dispatcher — . A coroutine started on will not block the main thread while suspended. As we have just launched a coroutine, we can invoke suspending operator and get the result. calls which shifts execution to and back to . As we are in the UI thread we can update LiveData by assigning a value.
  2. We can get rid of as is bound to a lifecycle. Any coroutine launched in this scope is automatically canceled if the is cleared.
BusinessProfileViewModel coroutines

Key takeaways

Reading and understanding code written with coroutines is quite easy, nonetheless it’s a paradigm shift that requires some effort to learn how to approach writing code with coroutines.

In this article I didn’t cover testing. I used the mockk library as I had issues testing coroutines using Mockito.

Everything I have written with RxJava I found quite easy to implement with coroutines, Flows and Channels. One of advantages of coroutines is that they are a Kotlin language feature and are evolving together with the language.

TransferWise Engineering

Posts from @TransferWise’s Engineering Team

Vadims Savjolovs

Written by

Product engineer at TransferWise

TransferWise Engineering

Posts from @TransferWise’s Engineering Team

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade