Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Kotlin Coroutines for Android

2307a37297162f815342545a2068b2f1?s=47 Erik Hellman
December 18, 2017

Kotlin Coroutines for Android

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

2307a37297162f815342545a2068b2f1?s=128

Erik Hellman

December 18, 2017
Tweet

More Decks by Erik Hellman

Other Decks in Programming

Transcript

  1. Kotlin Coroutines

  2. The Async Problem class AsyncExampleActivity : AppCompatActivity() { override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_async_example) } }
  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) } }
  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) } } }
  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) } } } }
  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) } } } }
  7. Callback Hell fun callbackHell(): Unit { loadA { loadB {

    loadC { loadD { loadE { displayResult(it) } } } } } }
  8. AsyncTask class ImageLoader(val imageView: ImageView) : AsyncTask<String, Void, Bitmap>() {

    override fun doInBackground(vararg params: String?): Bitmap { return ImageFetcher.fetchImage(params[0]!!) } override fun onPostExecute(result: Bitmap?) { imageView.setImageBitmap(result) } }
  9. RxJava Observable.just(URL) .map { ImageFetcher.fetchImage(it) } .subscribe { imageView.setImageBitmap(it) }

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

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

    } .observeOn(AndroidSchedulers.mainThread()) .subscribe { imageView.setImageBitmap(it) }
  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) })
  13. Coroutines

  14. Coroutines are cheap fun main(args: Array<String>) { (0 until 100000)

    .forEach { thread { sleep(5000L) // Fake our long running call print(".") } } }
  15. Coroutines are cheap fun main(args: Array<String>) = runBlocking<Unit> { val

    jobs = (0 until 100000) .map { launch { delay(5000L) // Fake our long running call print(".") } } // Wait for them to finish... jobs.forEach { it.join() } }
  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" }
  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 { ... }
  18. Kotlin Coroutines are easy! launch { val resultOne = doFirstThing(args[0].toInt())

    val resultTwo = doSecondThing(resultOne) val resultThree = doThirdThing(resultTwo) println("Result: $resultThree") }
  19. Cancellation fun main(args: Array<String>) = runBlocking<Unit> { 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.") }
  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]
  21. Behind the scenes

  22. Continuations // The following functions... suspend fun <T> CompletableFuture<T>.await(): T

    // ...is transformed at compile time to... fun <T> CompletableFuture<T>.await(continuation: Continuation<T>): Any?
  23. Continuations public interface Continuation<in T> { public val context: CoroutineContext

    public fun resume(value: T) public fun resumeWithException(exception: Throwable) }
  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)
  25. State machine class <anonymous_for_state_machine> extends CoroutineImpl<...> implements Continuation<Object> { //

    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()
  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 } }
  27. Looks familiar?

  28. None
  29. Kotlin Coroutines, Anko and Android

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

  31. Background jobs jobButton.setOnClickListener { async(UI) { val data: Deferred<String> =

    bg { loadData() } jobText.append(data.await()) } }
  32. Make it cancelable job = async(UI) { val data: Deferred<String>

    = bg { loadData() } // Waits until return, or throw CancellationException jobText.append(data.await()) } // in onDestroy() job?.cancel()
  33. Using ref() val ref: Ref<BackgroundJobs> = this.asReference() jobButton.setOnClickListener { async(UI)

    { val data: Deferred<String> = bg { loadData() } // Will throw CancellationException if null ref().jobText.append(data.await()) } }
  34. Click throttling

  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") }
  36. At most one at a time // Create an actor

    that reacts to new events, but only one at a time val jobActor = actor<Unit>(UI) { for (event in channel) doJob() } // Trigger new event on cick jobButton.setOnClickListener { jobActor.offer(Unit) }
  37. Producer/Consumer

  38. Producer // Setup producer val channel = produce { val

    cursor = queryDatabase() cursor.use { while (it.moveToNext()) { send(cursorToData(it)) } } }
  39. Consumer async(UI) { val cat = channel?.receiveOrNull() cat?.let { catAdapter.cats

    += it catAdapter.notifyItemInserted(catAdapter.cats.lastIndex) } }
  40. Subroutines are special cases of ... coroutines. — Donald Knuth

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

  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