A glimpse of Async-Await on Android

Kotlin 1.1 will bring coroutines to the language, which allows computations to be suspended at some points and continue later on. The obvious example is async-await, as introduced a couple of years ago in C#.

Every Android developer knows that when you deal with network requests and other I/O tasks, you will need to make sure you don’t block the main thread, and don’t touch the UI from a background thread.
Over the years dozens of techniques have come and gone. This article lists a few of the most used ones, and shows an example of the goodness that async-await can bring.

The scenario

We will fetch a user instance from the Github api and store it in some database. When this is done we show the result on-screen.
I won’t be explaining the techniques, as they should speak for themselves.

Plain old threads

Manual, full control

fun threads() {
val handler = Handler()

Thread {
try {
val user = githubApi.user()
userRepository.store(user)
handler.post {
threadsTV.text = "threads: [$user]"
}
} catch(e: IOException) {
handler.post {
threadsTV.text = "threads: [User retrieval failed.]"
}
}
}.start()
}

Android’s AsyncTask

Nobody uses these anymore, right?

fun asyncTask() {
object : AsyncTask<Unit, Unit, GithubUser?>() {

private var exception: IOException? = null

override fun doInBackground(vararg params: Unit): GithubUser? {
try {
val user = githubApi.user()
userRepository.store(user)
return user
} catch(e: IOException) {
exception = e
return null
}
}

override fun onPostExecute(user: GithubUser?) {
if (user != null) {
asyncTaskTV.text = "asyncTask: [$user]"
} else {
asyncTaskTV.text = "asyncTask: [User retrieval failed.]"
}
}
}.execute()
}

Callbacks

Callback-hell, anyone?

fun callbacks() {
githubApi.userFromCall().enqueue(object : Callback<GithubUser> {

override fun onResponse(call: Call<GithubUser>, response: Response<GithubUser>) {
val user = response.body()
userRepository.storeCallback(user) {
callbacksTV.text = "callbacks: [$user]"
}
}

override fun onFailure(call: Call<GithubUser>, t: Throwable) {
if (t is IOException)
callbacksTV.text = "callbacks: [User retrieval failed.]"
else
throw t
}
})
}

Rx

Getting to the good stuff..

fun rx() {
githubApi.userRx()
.flatMap { user ->
userRepository.storeRx(user).toSingle { user }
}
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ user ->
rxTV.text = "rx: [$user]"
},
{ throwable ->
if (throwable is IOException)
rxTV.text = "rx: [User retrieval failed.]"
else
throw throwable
}
)
}

Async-Await

Now would you look at that?

fun asyncAwait() = asyncUI {
try {
val user = await(githubApi.userAsync())
await(userRepository.storeAsync(user))
asyncAwaitTV.text = "asyncAwait: [$user]"
} catch(e: IOException) {
asyncAwaitTV.text = "asyncAwait: [User retrieval failed.]"
}
}

Here, the asyncUI (and similarly async<T>) function enables the coroutine functionality, which grants access to the await function. Whenever an execution reaches await, it will suspend computation of the function until the parameter has completed, but it will not block the calling thread. After that the coroutine will continue. The asyncUI function ensures that the coroutine continues on the main thread.


As you can see, coroutines can greatly improve readability of your code. Coroutines are currently available in the 1.1-M02 version of Kotlin.
The async-await code in the last section uses a library I wrote to utilize coroutines on Android. Want to learn more about coroutines? Check out the informal description.

In a follow-up article I take a deeper dive into Async-Await on Android.

Note: this article doesn’t deal with any cancellation or removal of listeners that may reference the Activity. Each of the techniques can provide a similar way of managing this to prevent leaks.