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

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. The Async Problem class AsyncExampleActivity : AppCompatActivity() { override fun

    onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_async_example) } }
  2. 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) } }
  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) thread { 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) runOnUiThread { 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 { if(!isDestroyed) imageView.setImageBitmap(image) } } } }
  6. Callback Hell fun callbackHell(): Unit { loadA { loadB {

    loadC { loadD { loadE { displayResult(it) } } } } } }
  7. 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) } }
  8. RxJava RxView.clicks(button) .map { URL } .observeOn(Schedulers.io()) .map { ImageFetcher.fetchImage(it)

    } .observeOn(AndroidSchedulers.mainThread()) .subscribe { imageView.setImageBitmap(it) }
  9. 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) })
  10. Coroutines are cheap fun main(args: Array<String>) { (0 until 100000)

    .forEach { thread { sleep(5000L) // Fake our long running call print(".") } } }
  11. 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() } }
  12. 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" }
  13. 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 { ... }
  14. Kotlin Coroutines are easy! launch { val resultOne = doFirstThing(args[0].toInt())

    val resultTwo = doSecondThing(resultOne) val resultThree = doThirdThing(resultTwo) println("Result: $resultThree") }
  15. 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.") }
  16. 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]
  17. 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?
  18. Continuations public interface Continuation<in T> { public val context: CoroutineContext

    public fun resume(value: T) public fun resumeWithException(exception: Throwable) }
  19. 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)
  20. 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()
  21. 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 } }
  22. 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()
  23. 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()) } }
  24. 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") }
  25. 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) }
  26. Producer // Setup producer val channel = produce { val

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

    += it catAdapter.notifyItemInserted(catAdapter.cats.lastIndex) } }
  28. 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