$30 off During Our Annual Pro Sale. View Details »

Kotlin Coroutines for Android

Erik Hellman
December 18, 2017

Kotlin Coroutines for Android

A walkthrough of Kotlin Coroutines and how to use it in Android Development

Erik Hellman

December 18, 2017
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

  1. Kotlin Coroutines

    View Slide

  2. The Async Problem
    class AsyncExampleActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_async_example)
    }
    }

    View Slide

  3. The Async Problem
    class AsyncExampleActivity : AppCompatActivity() {
    companion object {
    const val URL = "http://server.com/image.jpg"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_async_example)
    val image = ImageFetcher.fetchImage(URL)
    imageView.setImageBitmap(image)
    }
    }

    View Slide

  4. The Async Problem
    class AsyncExampleActivity : AppCompatActivity() {
    companion object {
    const val URL = "http://server.com/image.jpg"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_async_example)
    thread {
    val image = ImageFetcher.fetchImage(URL)
    imageView.setImageBitmap(image)
    }
    }
    }

    View Slide

  5. The Async Problem
    class AsyncExampleActivity : AppCompatActivity() {
    companion object {
    const val URL = "http://server.com/image.jpg"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_async_example)
    thread {
    val image = ImageFetcher.fetchImage(URL)
    runOnUiThread {
    imageView.setImageBitmap(image)
    }
    }
    }
    }

    View Slide

  6. The Async Problem
    class AsyncExampleActivity : AppCompatActivity() {
    companion object {
    const val URL = "http://server.com/image.jpg"
    }
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    setContentView(R.layout.activity_async_example)
    thread {
    val image = ImageFetcher.fetchImage(URL)
    runOnUiThread {
    if(!isDestroyed) imageView.setImageBitmap(image)
    }
    }
    }
    }

    View Slide

  7. Callback Hell
    fun callbackHell(): Unit {
    loadA {
    loadB {
    loadC {
    loadD {
    loadE { displayResult(it) }
    }
    }
    }
    }
    }

    View Slide

  8. AsyncTask
    class ImageLoader(val imageView: ImageView) : AsyncTask() {
    override fun doInBackground(vararg params: String?): Bitmap {
    return ImageFetcher.fetchImage(params[0]!!)
    }
    override fun onPostExecute(result: Bitmap?) {
    imageView.setImageBitmap(result)
    }
    }

    View Slide

  9. RxJava
    Observable.just(URL)
    .map { ImageFetcher.fetchImage(it) }
    .subscribe { imageView.setImageBitmap(it) }

    View Slide

  10. RxJava
    Observable.just(URL)
    .map { ImageFetcher.fetchImage(it) }
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { imageView.setImageBitmap(it) }

    View Slide

  11. RxJava
    RxView.clicks(button)
    .map { URL }
    .observeOn(Schedulers.io())
    .map { ImageFetcher.fetchImage(it) }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { imageView.setImageBitmap(it) }

    View Slide

  12. RxJava
    RxView.clicks(button)
    .map { URL }
    .observeOn(Schedulers.io())
    .map { ImageFetcher.fetchImage(it) }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe({ imageView.setImageBitmap(it) },
    { Log.e(TAG, "Couldn't fetch image!", e) })

    View Slide

  13. Coroutines

    View Slide

  14. Coroutines are cheap
    fun main(args: Array) {
    (0 until 100000)
    .forEach {
    thread {
    sleep(5000L) // Fake our long running call
    print(".")
    }
    }
    }

    View Slide

  15. Coroutines are cheap
    fun main(args: Array) = runBlocking {
    val jobs = (0 until 100000)
    .map {
    launch {
    delay(5000L) // Fake our long running call
    print(".")
    }
    }
    // Wait for them to finish...
    jobs.forEach { it.join() }
    }

    View Slide

  16. Kotlin Coroutines are easy!
    kotlin {
    experimental {
    coroutines 'enable'
    }
    }
    dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    implementation “org.jetbrains.kotlinx:kotlinx-coroutines-core:0.20"
    implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.20"
    }

    View Slide

  17. Kotlin Coroutines are easy!
    // Three suspending functions
    suspend fun doFirstThing(input: Int): String { ... }
    suspend fun doSecondThing(input: String): Int { ... }
    suspend fun doThirdThing(input: Int): String { ... }

    View Slide

  18. Kotlin Coroutines are easy!
    launch {
    val resultOne = doFirstThing(args[0].toInt())
    val resultTwo = doSecondThing(resultOne)
    val resultThree = doThirdThing(resultTwo)
    println("Result: $resultThree")
    }

    View Slide

  19. Cancellation
    fun main(args: Array) = runBlocking {
    val job = launch {
    while(true) {
    println("I'm sleeping...")
    delay(500L)
    }
    }
    delay(1300L) // delay a bit
    println("main: I'm tired of waiting!")
    job.cancel() // cancels the job
    job.join() // waits for job's completion
    println("main: Now I can quit.")
    }

    View Slide

  20. Sequences
    fun fibonacci() = buildSequence {
    var terms = Pair(0, 1)
    // this sequence is infinite
    while(true) {
    yield(terms.first)
    terms = Pair(terms.second, terms.first + terms.second)
    }
    }
    println(fibonacci().take(10).toList()) // [0, 1, 1, 2, 3, 5, 8, 13, 21, 34]

    View Slide

  21. Behind the scenes

    View Slide

  22. Continuations
    // The following functions...
    suspend fun CompletableFuture.await(): T
    // ...is transformed at compile time to...
    fun CompletableFuture.await(continuation: Continuation): Any?

    View Slide

  23. Continuations
    public interface Continuation {
    public val context: CoroutineContext
    public fun resume(value: T)
    public fun resumeWithException(exception: Throwable)
    }

    View Slide

  24. State machine
    val a = a()
    val y = foo(a).await() // suspension point #1
    b()
    val z = bar(a, y).await() // suspension point #2
    c(z)

    View Slide

  25. State machine
    class extends CoroutineImpl<...> implements Continuation {
    // The current state of the state machine
    int label = 0
    // local variables of the coroutine
    A a = null
    Y y = null
    void resume(Object data) {
    if (label == 0) goto L0
    if (label == 1) goto L1
    if (label == 2) goto L2
    else throw IllegalStateException()

    View Slide

  26. State machine
    L0:
    // data is expected to be `null` at this invocation
    a = a()
    label = 1
    data = foo(a).await(this) // 'this' is passed as a continuation
    if (data == COROUTINE_SUSPENDED) return // return if await had suspended execution
    L1:
    // external code has resumed this coroutine passing the result of .await() as data
    y = (Y) data
    b()
    label = 2
    data = bar(a, y).await(this) // 'this' is passed as a continuation
    if (data == COROUTINE_SUSPENDED) return // return if await had suspended execution
    L2:
    // external code has resumed this coroutine passing the result of .await() as data
    Z z = (Z) data
    c(z)
    label = -1 // No more steps are allowed
    return
    }
    }

    View Slide

  27. Looks familiar?

    View Slide

  28. View Slide

  29. Kotlin Coroutines,
    Anko and Android

    View Slide

  30. Anko
    dependencies {
    implementation "org.jetbrains.anko:anko-coroutines:0.10.3"
    }

    View Slide

  31. Background jobs
    jobButton.setOnClickListener {
    async(UI) {
    val data: Deferred = bg {
    loadData()
    }
    jobText.append(data.await())
    }
    }

    View Slide

  32. Make it cancelable
    job = async(UI) {
    val data: Deferred = bg {
    loadData()
    }
    // Waits until return, or throw CancellationException
    jobText.append(data.await())
    }
    // in onDestroy()
    job?.cancel()

    View Slide

  33. Using ref()
    val ref: Ref = this.asReference()
    jobButton.setOnClickListener {
    async(UI) {
    val data: Deferred = bg {
    loadData()
    }
    // Will throw CancellationException if null
    ref().jobText.append(data.await())
    }
    }

    View Slide

  34. Click throttling

    View Slide

  35. At most one at a time
    suspend fun doJob() {
    jobText.append("Job started...\n")
    delay(1000)
    jobText.append("still working...\n")
    delay(1000)
    jobText.append("just a bit more...\n")
    delay(1000)
    jobText.append("Job done!\n")
    }

    View Slide

  36. At most one at a time
    // Create an actor that reacts to new events, but only one at a time
    val jobActor = actor(UI) { for (event in channel) doJob() }
    // Trigger new event on cick
    jobButton.setOnClickListener {
    jobActor.offer(Unit)
    }

    View Slide

  37. Producer/Consumer

    View Slide

  38. Producer
    // Setup producer
    val channel = produce {
    val cursor = queryDatabase()
    cursor.use {
    while (it.moveToNext()) {
    send(cursorToData(it))
    }
    }
    }

    View Slide

  39. Consumer
    async(UI) {
    val cat = channel?.receiveOrNull()
    cat?.let {
    catAdapter.cats += it
    catAdapter.notifyItemInserted(catAdapter.cats.lastIndex)
    }
    }

    View Slide

  40. Subroutines are special
    cases of ... coroutines.
    — Donald Knuth

    View Slide

  41. Thank you for listening!
    Slides available at https://speakerdeck.com/erikhellman/kotlin-
    coroutines-for-android

    View Slide

  42. Resources
    • https://kotlinlang.org/docs/reference/coroutines.html
    • https://github.com/Kotlin/kotlinx.coroutines/blob/master/coroutines-
    guide.md
    • https://github.com/Kotlin/kotlinx.coroutines/blob/master/ui/coroutines-
    guide-ui.md
    • https://github.com/Kotlin/kotlin-coroutines/blob/master/kotlin-coroutines-
    informal.md
    • https://github.com/Kotlin/anko/wiki/Anko-Coroutines
    • https://github.com/ErikHellman/KotlinCoroutinesWithAndroid

    View Slide