Migrate from RxJava to Kotlin Coroutines: A step by step guide
I think a lot of us are in the same boat: Threading and Concurrency are things we’ve gotten under control with RxJava, but we don’t really have full comprehension of how RxJava works, or all that it can do, and the learning curve is pretty steep. Enter: Kotlin Coroutines. A simple, straightforward T&C solution with first class support in Android.
…Well, not exactly. There are a few things in that talk that they don’t really mention, which makes switching your existing RxJava code to Coroutines a little bit tricky. I’ve gone ahead and figured all that out for you, so all you need to do is sit back, relax, and let me do all the work.
Disclaimer
What I’m about to show you is just one interpretation of how Coroutines could be written, and I’ll only cover my most common use case: I need to make a network call, get data from an API and then use that data to update the UI.
Setup
So the first thing you’ll need to do is setup your project with all the dependencies you’ll need to pull this off. For this example, I’m using Retrofit and Moshi.
For now, you’ll need a snapshot version of Retrofit. If you’ve never used a snapshot, the first thing you’ll need to do is open up your project level build.gradle file and add the snapshot repo to your repositories closure:
Next, you’ll need a bunch ‘o libraries to get up and running… here’s all of them, with the specific versions (as of this writing) of each that you’ll need:
Now that that’s all taken care of, we can finally start writing some code.
Retrofit
Let’s start with the old code. First, you probably have a retrofit service and API call, and it probably looks something like this:
Fine, right? You get an Observable back, you subscribe and observe, and you have no worries. Now, take a look at the Coroutines version:
Notice that, in the Coroutines version, no Call Adapter Factory is necessary at all. Now, notice that instead of an Observable, the API Request function call just returns the actual object you want with no wrapper. Retrofit 2.5.1-SNAPSHOT supports the suspend modifier, so you don’t need an Observable or Deferred or Call — or any wrapper at all. This function now reads exactly like a synchronous call. It’s easy to read, and easy to use. How? Read on.
RxJava
Next, you probably have a call to get that Observable, subscribe and observe the result. That might look like this:
Great. You’re getting Observable, setting the threads you want everything to operate on via the schedulers, and reacting to (some of) it’s events. You don’t really understand why onNext gets called but onComplete doesn’t, and you’ve thought about converting to a Single, but you aren’t sure if that’s right. You have some error handling, and you’re even disposing when you pause, so that you don’t leak your callback. Perfect. But what if it could just be this:
Wait, what? What happened to the onPause? Where’s the subscription?
With Coroutines, you don’t need either. In the Android Jetpack Kotlin Lifecycle library (androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha01), the folks at Google have added a very handy little tool call LifeCycleCoroutineScope. Every lifecycle owner (i.e. activities and fragments) now has its own Coroutine Scope which handles two tricky problems for you when you use the launchWhenStarted convenience function:
- The scope will automatically suspend execution of Coroutine when the lifecycle state is not at least started. So when your lifecycle owner becomes paused or stopped, your Coroutine will follow suit — as long as all the asynchronous calls inside your Coroutine are suspend functions.
- The scope will also automatically cancel your Coroutine when the lifecycle state is destroyed. No leaks, no Disposable tracking, no code to write to handle it.
You’ll also notice that instead of using an error handling callback, you can just use a regular ol’ try/catch to handle your exceptions. This reinforces the semantics that, with Coroutines, async code reads just like synchronous code.
The only part you need to actively manage is making sure that you run your UI code on the main thread. You do this by simply putting that main-thread code into the withContext closure, and giving that closure the Main Dispatcher as an argument. And remember that, to get access to that Main dispatcher for Android, you need to use the Kotlin Coroutines Android library (org.jetbrains.kotlinx:kotlinx-coroutines-android:1.2.1), or it won’t resolve at all.
Finally
Ok, so you set up your dependencies, migrated your Retrofit calls, and implemented calling your Coroutines, so that only thing left to do is — nothing, you’re done. That’s basically it.
We simplified our code, removed callbacks, implemented error handling, and we’re up and running with a Coroutines solution that’s simple, easy to read, and straightforward.
Now just go through every network call in your app, and rinse and repeat. Enjoy.