1. 程式人生 > >A glimpse of Async-Await on Android

A glimpse of Async-Await on Android

A glimpse of Async-Await on Android

Note: this article was written for a preview version of coroutines for Kotlin. Its details have changed since then.

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.