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

Kotlin Coroutines in Android

Kotlin Coroutines in Android

Charles Julian Knight

September 22, 2017
Tweet

More Decks by Charles Julian Knight

Other Decks in Technology

Transcript

  1. Outline - The Problem - Existing Solutions - Intro to

    Coroutines - Android-Specific Patterns
  2. We’ve gotten pretty good at this... - AsyncTask - Callbacks

    - Promises / Futures - Observables / Reactive Streams
  3. Simple Flow Example Show Confirm Dialog Append Result Network Error

    error Canceled confirmed? Load Words from Network Count usage of word in file For each yes no Loading
  4. We’ve gotten pretty good at this... - AsyncTask - Callbacks

    - Promises / Futures - Observables / Reactive Streams
  5. We’ve gotten pretty good at this... - AsyncTask - Boilerplate,

    blocking only, hard to chain - Callbacks - Promises / Futures - Observables / Reactive Streams
  6. We’ve gotten pretty good at this... - AsyncTask - Boilerplate,

    blocking only, hard to chain - Callbacks - Callback hell, hard to refactor, no cancellation - Promises / Futures - Observables / Reactive Streams
  7. We’ve gotten pretty good at this... - AsyncTask - Boilerplate,

    blocking only, hard to chain - Callbacks - Callback hell, hard to refactor, no cancellation - Promises / Futures - CompletableFuture minSdk 24, no cancellation, concurrent by default - Observables / Reactive Streams
  8. We’ve gotten pretty good at this... - AsyncTask - Boilerplate,

    blocking only, hard to chain - Callbacks - Callback hell, hard to refactor, no cancellation - Promises / Futures - CompletableFuture minSdk 24, no cancellation, concurrent by default - Observables / Reactive Streams - Steep learning curve, different paradigm, too many operators
  9. “Experimental” - Stable (as in not-buggy) - API is not

    finalized - Guaranteed backwards compatibility - Calls covered here are unlikely to change - IDE tools to ease migration Can I use it in production? “Yes! You should.” - Roman Elizarov (seconded by me)
  10. Setup dependencies { compile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.18' compile 'org.jetbrains.kotlinx:kotlinx-coroutines-android:0.18' // RxJava 1

    compile 'org.jetbrains.kotlinx:kotlinx-coroutines-rx1:0.18' // RxJava 2 compile 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:0.18' // Java 8 CompletableFuture (minSdk 24) compile 'org.jetbrains.kotlinx:kotlinx-coroutines-jdk8:0.18' // Java NIO (minSdk 26) compile 'org.jetbrains.kotlinx:kotlinx-coroutines-nio:0.18' } kotlin { experimental { coroutines 'enable' } }
  11. Creating Coroutines suspend fun delayedParseInt(i: String): Int { return suspendCoroutine

    { cont -> // this code gets executed immediately to set up the coroutine Thread { Thread.sleep(1000) // some time later, use the continuation to resume from suspension try { val result = i.toInt() cont.resume(result) }catch (e: NumberFormatException) { cont.resumeWithException(e) } }.start() // after leaving this method, the caller will suspend until resume is called } }
  12. Combining Coroutines suspend fun doFirst(): String suspend fun doSecond(): Boolean

    suspend fun doThird(data: String) suspend fun example() { val data = doFirst() if (doSecond()) { doThird(data) } }
  13. Coroutine Builders val job = launch(UI) { example() } job.isActive

    // true launch(CommonPool) { delay(100) print("ok") }
  14. Coroutine Context Where the non-suspending code is run. Or another

    way: What thread picks back up after a suspension resumes. - UI - CommonPool - newSingleThreadContext(name = "single") - newFixedThreadPoolContext(nThreads = 3, name = "pool")
  15. Concurrency - Async/Await val one = async(CommonPool) { delay(100) return@async

    1 } // one is a Promise (Deferred<Int>). It has already started val two = async(CommonPool) { delay(100) return@async 2 } // now both promises are running // await() suspends until the promise resolves val sum = one.await() + two.await()
  16. Under the Hood void example(Continuation cont) { switch (cont.label) {

    case 0: cont.label = 1; showConfirmDialog(cont); break; case 1: if (cont.lastError != null) throw cont.lastError; boolean isConfirmed = (boolean) cont.lastValue; if (!isConfirmed) { textView.setText("cancelled"); return; } textView.setText("loading"); makeNetworkRequest(cont); break; case 2: if (cont.lastError != null) throw cont.lastError;
  17. Wrapping Callback APIs suspend fun <T> Task<T>.await(): T = suspendCoroutine

    { cont -> addOnCompleteListener { cont.resume(it.result) } addOnFailureListener { cont.resumeWithException(it) } } launch(UI) { val locationClient = LocationServices.getFusedLocationProviderClient(this) val location = locationClient.lastLocation.await() }
  18. - Single.await() - Observable.awaitFirst() - Observable.awaitFirstOrDefault() - Observable.awaitSingle() - Observable.awaitLast()

    - Observable.consumeEach { } Errors and cancellation work too. Convert Observables to Coroutines
  19. Coroutine Dialogs suspend fun showConfirmationDialog() = suspendCancellableCoroutine<Boolean> { cont ->

    val dialog = AlertDialog.Builder(this) .setMessage("Are you sure?") .setPositiveButton("Yes", { _, _ -> cont.resume(true) }) .setNegativeButton("No", { _, _ -> cont.resume(false) }) .setCancelable(true) .setOnCancelListener { cont.cancel() } .create() dialog.show() cont.invokeOnCompletion { if(cont.isCancelled) dialog.dismiss() } }
  20. Coroutine Activity Results interface ActivityResultMixin { data class ActivityResult(val requestCode:

    Int, val resultCode: Int, val data: Intent?) fun startActivityForResult(intent: Intent, requestCode: Int) val _activityResultStream: PublishSubject<ActivityResult> suspend fun startActivityForResultAsync(intent: Intent, requestCode: Int): ActivityResult { startActivityForResult(intent, requestCode) return _activityResultStream .filter { it.requestCode == requestCode } .awaitFirstOrDefault((ActivityResult(requestCode, Activity.RESULT_CANCELED, null))) } fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { _activityResultStream.onNext(ActivityResult(requestCode, resultCode, data)) } fun onDestroy() { _activityResultStream.onCompleted() } }
  21. Blocking Code suspend fun saveUser(db: SQLiteDatabase, user: User) = async(CommonPool)

    { db.insert("users", null, ContentValues().apply { put("id", user.id) put("name", user.name) put("email", user.email) }) }.await()
  22. Job Lifecycle class JobCancellationActivity : Activity() { var job: Job?

    = null override fun onStart() { super.onStart() job = launch(UI) { /* ... */ } } override fun onStop() { job?.cancel(CancellationException("onStop")) super.onStop() } }
  23. Resources - Kotlin Coroutines Guide: github.com/Kotlin/kotlinx.coroutines - Introduction to Kotlin

    coroutines - Roman Elizarov (GeekOut 2017) vimeo.com/222499934 - Examples from this talk: github.com/rabidaudio/kotlin-coroutines-android