Kotlin Asynchronicity – Why use Rx? Concurrency Part 1

One of the biggest challenges with developing Android applications is executing tasks on a background thread. Java by default has the Runnable interface, where you override the run method and put your async code in there. When Google adopted Java for Android, they released a new type of class called AsyncTask, with the hopes of making asynchronous tasks in Android a lot easier. And they were kinda right – it certainly was easier than Runnable, but the API felt like it was written by people who didn’t know how Activities or Fragments worked. I’m gonna take this opportunity to trash the API a bit more:

  • Ugly API – like, really ugly
  • Creating a new class that extends AsyncTask for every async task you want
  • Unaware of Activity lifecycle – if the activity is destroyed, the AsyncTask doesn’t know about it by default in onPostExecute unless you tell it
  • Cancelling an AsyncTask just puts it in a cancelled state – it’s up to you to check whether it’s been cancelled and halt operation.

When I first started looking into Android development I was shocked by the amount of people that used RxJava and RxAndroid. I knew they existed, and had looked into RxSwift, but thought the drawbacks of including RxSwift outweighed its benefits. But I understand it with Java. Starting threads in Java, swapping over to the main thread – it’s all so much more difficult than it should be. Swift has GCD, and the API for this was dramatically improved in Swift 3. Async operations in Swift, while it can be improved (I’ll talk about this at some other point), are much easier than in Java. But now Kotlin’s here.

There’s this library you can include in your grade file:

‘compile org.jetbrains.kotlinx:kotlinx-corountines-core:0.19.2’

and then in the build.gradle file, add:

kotlin {

experimental {

coroutines ‘enable’

}

}

If you enable coroutines in the experimental features you can start using these cool ideas. You need at least to be using Kotllin 1.1.4, but it’s better to use the latest version because it fixes some bugs.

Let’s start with coroutines. These are very lightweight, thread-like blocks which can be started and suspended, but they can then be resumed on another thread. Starting a coroutine is very simple. The syntax looks like:

launch(CommonPool) {}

Where CommonPool is the thread context. You can easily use the UI thread by just passing UI, or you can create your own thread context like:

launch(newSingleThreadContext(“MyOwnThread”)) {}

Ok, so we have a coroutine. Now let’s put put some code in here. Code in a coroutine is written sequentially which makes it a lot easier to read and understand.

Here’s a really basic example:

fun doSomething() = runBlocking<Unit> {

        val job = launch {

        delay(1000L)

        print(“World!”)

        }

    print(“Hello, ”)

    job.join()

}

This will print “Hello, World!”. runBlocking<Unit> just means to run the following code on the current thread and block execution of everything else. Pretty simple.

As well as launch {}, there’s also async {}. These are very similar with one difference – launch returns a Job, and async returns a Deferred object. A job doesn’t carry any resulting value, whereas Deferred is a lightweight, non-blocking future that represents a promise to provide a result at some point in the future. Deferred is also a Job so you can cancel it. You can also use .await() on a Deferred value to get its eventual result.

Let’s imagine you have two functions, both of which do asynchronous tasks, and both need to return an eventual result. You can use suspending functions and async {} here, and write your code sequentially.

suspend fun doSomethingGood(): Int {

    delay(1000L)

    return 12

}

suspend fun doAnotherGoodThing(): Int {

    delay(1000L)

    return 19

}

fun addResults() = runBlocking<Unit> {

    val firstResult = doSomethingGood()

    val secondResult = doAnotherGoodThing()

    print(“The answer is ${firstResult + secondResult}”)

}

First, take a look at the functions. They are modified with the word suspend. This just means that the function can be suspended. Remember how coroutines can be suspended? When you call a suspending function from within a coroutine, the coroutine will be suspended until the value of that function call is returned, at which point the coroutine will resumed. This is why you can write code sequentially even though the functions are asynchronous. Suspending functions can only be called from other suspending functions or from inside a coroutine.

You’ll notice some other cool things here. The two functions actually just return a value like normal, rather than invoking a higher order function or callback. Second, the code inside addResults runs and reads sequentially, so it will first call doSomethingGood(), wait until the result comes back, and then call doAnotherGoodThing(), wait until its result comes back, and then print.

If you want to have a bunch of functions all run asynchronously and concurrently, you can use the async function and call .await() on the return value, which is of type Deferred:

fun addResults() = runBlocking<Unit> {

    val firstResult = async { doSomethingGood() }

    val secondResult = async { doAnotherGoodThing() }

    print(“The answer is ${firstResult.await() + secondResult.await()}”)

}

This will call both functions concurrently, and only when both have returned will the print be called. You call .await() on the async function to initiate the async work passed in to the lambda.

The preferred syntax for this is to create another function with the word async at the start, like this:

fun asyncDoSomethingGood = async {

    doSomethingGood()

}

and then instead do:

val firstResult = asyncDoSomethingGood()

This was just a high level overview of using async/await and coroutines in Kotlin, but you can read up in much more detail about this stuff here: https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-guide.md#composing-suspending-functions

So with asynchronicity now much better with Kotlin than Java, with lifecycle aware LiveData that fires when the value changes, the MVVM design pattern, higher-order functions like map and fold and filter – do you really need Rx?

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Twitter picture

You are commenting using your Twitter account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s