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

Android Coroutine Recipes

Android Coroutine Recipes

Presentation Source: https://goo.gl/fTP5or

Brief introduction to Kotlin coroutines with practical android samples.

Dmytro Danylyk

March 20, 2018
Tweet

More Decks by Dmytro Danylyk

Other Decks in Technology

Transcript

  1. Android Coroutine
    Recipes
    Dmytro Danylyk
    Atlassian

    View full-size slide

  2. Coroutines simplify asynchronous programming by providing
    possibility to write code in direct style (sequentially).

    View full-size slide

  3. Thread { code }
    .start()
    What we have

    View full-size slide

  4. Thread { code }
    .start()
    What we have

    View full-size slide

  5. object MyTask: AsyncTask() {
    override fun doInBackground { code }
    override fun onPostExecute { code }
    }
    Thread { code }
    .start()
    What we have

    View full-size slide

  6. object MyTask: AsyncTask() {
    override fun doInBackground { code }
    override fun onPostExecute { code }
    }
    Thread { code }
    .start()
    What we have

    View full-size slide

  7. object MyTask: AsyncTask() {
    override fun doInBackground { code }
    override fun onPostExecute { code }
    }
    Observable.just(1,2,3,4)
    .map { code }
    .doOnEach { code }
    .doOnError { code }
    .subscribe(
    { onNext },
    { onError }
    )
    Thread { code }
    .start()
    What we have

    View full-size slide

  8. object MyTask: AsyncTask() {
    override fun doInBackground { code }
    override fun onPostExecute { code }
    }
    Observable.just(1,2,3,4)
    .map { code }
    .doOnEach { code }
    .doOnError { code }
    .subscribe(
    { onNext },
    { onError }
    )
    Thread { code }
    .start()
    What we have

    View full-size slide

  9. object MyTask: AsyncTask() {
    override fun doInBackground { code }
    override fun onPostExecute { code }
    }
    Observable.just(1,2,3,4)
    .map { code }
    .doOnEach { code }
    .doOnError { code }
    .subscribe(
    { onNext },
    { onError }
    )
    Thread { code }
    .start()
    What we have

    View full-size slide

  10. What we have
    What we want

    View full-size slide

  11. What we want
    fun loadData() = launch {
    view.showLoading()
    val result = async { // computation }.await()
    view.showData(result)
    view.hideLoading()
    }

    View full-size slide

  12. Coroutine building blocks

    View full-size slide

  13. coroutine builder coroutine context suspend function
    builders to launch
    coroutine
    coroutine dispatchers mark suspension point

    View full-size slide

  14. withContext()
    T
    returns result
    uncaught exception
    crash app

    View full-size slide

  15. launch()
    Job
    fire and forget
    uncaught exception
    crash app
    withContext()
    T
    returns result
    uncaught exception
    crash app

    View full-size slide

  16. launch() async()
    Job Deferred
    fire and forget
    returns non-blocking
    future
    uncaught exception
    crash app
    withContext()
    T
    returns result
    uncaught exception
    crash app
    uncaught exception is
    stored inside the
    resulting Deferred

    View full-size slide

  17. CoroutineContext
    UI
    dispatch execution into
    Android main thread

    View full-size slide

  18. CoroutineContext
    UI CommonPool
    dispatch execution into
    background thread
    dispatch execution into
    Android main thread

    View full-size slide

  19. CoroutineContext
    UI Unconfined
    CommonPool
    dispatch execution into
    background thread
    dispatch execution into
    Android main thread
    dispatch execution into
    current thread

    View full-size slide

  20. suspend function
    suspend fun loadData(): Data
    fun loadData(listener: Continuation)
    transforms to

    View full-size slide

  21. How to launch coroutine
    // kotlinx.coroutines.experimental.android
    val uiContext: CoroutineContext = UI
    // kotlinx.coroutines.experimental
    val bgContext: CoroutineContext = CommonPool

    View full-size slide

  22. How to launch coroutine
    private fun loadData() {
    view.showLoading()
    val result = dataProvider.provideData()
    view.showData(result)
    view.hideLoading()
    }

    View full-size slide

  23. How to launch coroutine
    private fun loadData() = launch(uiContext) {
    view.showLoading()
    val result = dataProvider.provideData()
    view.showData(result)
    view.hideLoading()
    }
    parent coroutine

    View full-size slide

  24. How to launch coroutine
    private fun loadData() = launch(uiContext) {
    view.showLoading()
    val result = withContext(bgContext) { dataProvider.provideData() }
    view.showData(result)
    view.hideLoading()
    }
    child coroutine

    View full-size slide

  25. How to launch coroutine
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    val result = withContext(bgContext) { dataProvider.provideData() } // bg
    view.showData(result) // ui thread
    view.hideLoading()
    }

    View full-size slide

  26. Launch two tasks sequentially
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    // non ui thread, suspend until task is finished
    val result1 = withContext(bgContext) { dataProvider.provideData() }
    // non ui thread, suspend until task is finished
    val result2 = withContext(bgContext) { dataProvider.provideData() }
    val result = "$result1 $result2" // ui thread
    view.showData(result)
    view.hideLoading()
    }

    View full-size slide

  27. Launch two tasks sequentially
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    // non ui thread, suspend until task is finished
    val result1 = withContext(bgContext) { dataProvider.provideData() }
    // non ui thread, suspend until task is finished
    val result2 = withContext(bgContext) { dataProvider.provideData() }
    val result = "$result1 $result2" // ui thread
    view.showData(result)
    view.hideLoading()
    }

    View full-size slide

  28. Launch two tasks in parallel
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    val task1 = async(bgContext) { dataProvider.provideData() }
    val task2 = async(bgContext) { dataProvider.provideData() }
    // non ui thread, suspend until finished
    val result = "${task1.await()} ${task2.await()}"
    view.showData(result) // ui thread
    view.hideLoading()
    }

    View full-size slide

  29. Launch two tasks in parallel
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    val task1 = async(bgContext) { dataProvider.provideData() }
    val task2 = async(bgContext) { dataProvider.provideData() }
    // non ui thread, suspend until finished
    val result = "${task1.await()} ${task2.await()}"
    view.showData(result) // ui thread
    view.hideLoading()
    }

    View full-size slide

  30. Launch a coroutine with a timeout
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    // non ui thread, suspend until the task is finished
    // or return null in 2 sec
    val result = withTimeoutOrNull(2, TimeUnit.SECONDS) {
    withContext(bgContext) { dataProvider.provideData() }
    }
    view.showData(result) // ui thread
    view.hideLoading()
    }

    View full-size slide

  31. Launch a coroutine with a timeout
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    // non ui thread, suspend until the task is finished
    // or return null in 2 sec
    val result = withTimeoutOrNull(2, TimeUnit.SECONDS) {
    withContext(bgContext) { dataProvider.provideData() }
    }
    view.showData(result) // ui thread
    view.hideLoading()
    }

    View full-size slide

  32. Cancellation

    View full-size slide

  33. Сancel a coroutine
    private var job: Job? = null
    fun onNextClicked() {
    job = loadData()
    }
    fun onBackClicked() {
    job?.cancel()
    }
    private fun loadData() = launch(uiContext) {
    // code
    }

    View full-size slide

  34. Сancel a coroutine
    private var job: Job? = null
    fun onNextClicked() {
    job = loadData()
    }
    fun onBackClicked() {
    job?.cancel()
    }
    private fun loadData() = launch(uiContext) {
    // code
    }

    View full-size slide

  35. Сancel a coroutine
    private var job: Job? = null
    fun onNextClicked() {
    job = loadData()
    }
    fun onBackClicked() {
    job?.cancel()
    }
    private fun loadData() = launch(uiContext) {
    // code
    }

    View full-size slide

  36. Error handling

    View full-size slide

  37. try-catch block
    private fun loadData() = launch(uiContext) {
    view.showLoading() // ui thread
    try {
    val result = withContext(bgContext) { dataProvider.provideData() }
    view.showData(result) // ui thread
    } catch (e: IllegalArgumentException) {
    e.printStackTrace()
    }
    view.hideLoading()
    }

    View full-size slide

  38. Store inside Deferred
    private fun loadData() = async(uiContext) {
    view.showLoading() // ui thread
    val task = async(bgContext) { dataProvider.provideData() }
    val result = task.await() // non ui thread
    view.showData(result) // ui thread
    view.hideLoading()
    }

    View full-size slide

  39. Store inside Deferred
    var job: Deferred = loadData()
    job.invokeOnCompletion { it: Throwable? ->
    it?.printStackTrace()
    }

    View full-size slide

  40. Exception handler
    val exceptionHandler= CoroutineExceptionHandler { _, throwable ->
    throwable.printStackTrace()
    }
    private fun loadData() = launch(uiContext + exceptionHandler) {
    // code
    }

    View full-size slide

  41. Return null if exception was thrown
    /**
    * return T or null if exception was thrown
    */
    suspend fun Deferred.awaitSafe(): T? = try {
    await()
    } catch (e: Exception) {
    e.printStackTrace()
    null
    }

    View full-size slide

  42. Return null if exception was thrown
    private fun loadData() = launch(uiContext) {
    val task = async(bgContext) { dataProvider.provideData() }
    val result = task.awaitSafe()
    if(result != null) {
    // success
    } else {
    // failure
    }
    }

    View full-size slide

  43. Return Result
    /**
    * return Result which contain either success or failure
    */
    suspend fun Deferred.awaitResult(): Result = try {
    Result(success = await(), failure = null)
    } catch (e: Exception) {
    Result(success = null, failure = e)
    }
    data class Result(val success: T?, val failure: Exception?)

    View full-size slide

  44. Return Result
    private fun loadData() = launch(uiContext) {
    val task = async(bgContext) { dataProvider.provideData() }
    val (success, failure) = task.awaitResult()
    if(success != null) {
    // success T
    } else {
    // failure Exception
    }
    }

    View full-size slide

  45. Testing: Unconfined context
    class MainPresenter(val uiContext: CoroutineContext = UI,
    val bgContext: CoroutineContext = CommonPool) {
    fun loadData() = launch(uiContext) { ... }
    }

    View full-size slide

  46. Testing: Unconfined context
    @Test
    fun test() {
    val presenter = MainPresenter(Unconfined, Unconfined)
    // test
    presenter.loadData()
    // verify
    verify(mockView).showLoading()
    verify(mockDataProvider).provideData()
    }

    View full-size slide

  47. Miscellaneous

    View full-size slide

  48. Callback to coroutine
    fun loadData() {
    val docRef = db.collection("cities").document("NSW")
    docRef.get().addOnCompleteListener(OnCompleteListener { task ->
    if (task.isSuccessful) {
    // task.result.data
    } else {
    // task.exception
    }
    })
    }

    View full-size slide

  49. Callback to coroutine
    suspend fun loadData() = suspendCoroutine { continuation ->
    val docRef = db.collection("cities").document("NSW")
    docRef.get().addOnCompleteListener(OnCompleteListener { task ->
    if (task.isSuccessful) {
    // task.result.data
    } else {
    // task.exception
    }
    })
    }

    View full-size slide

  50. Callback to coroutine
    suspend fun loadData() = suspendCoroutine { continuation ->
    val docRef = db.collection("cities").document("NSW")
    docRef.get().addOnCompleteListener(OnCompleteListener { task ->
    if (task.isSuccessful) {
    continuation.resume(task.result)
    } else {
    continuation.resumeWithException(task.exception)
    }
    })
    }

    View full-size slide

  51. Callback to coroutine
    suspend fun loadData() = suspendCoroutine { continuation ->
    val docRef = db.collection("cities").document("NSW")
    docRef.get().addOnCompleteListener(OnCompleteListener { task ->
    if (task.isSuccessful) {
    continuation.resume(task.result)
    } else {
    continuation.resumeWithException(task.exception)
    }
    })
    }

    View full-size slide

  52. Coroutine modules
    // gradle
    implementation 'io.reactivex.rxjava2:rxjava:2.1.3'
    implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-rx2:0.19.1'
    fun loadData() = launch(bgContext) {
    val result = Single.fromCallable { "Jon Doe" }.await() // suspend
    }

    View full-size slide

  53. Coroutine modules
    // reactive
    implementation 'kotlinx-coroutines-reactive'
    implementation 'kotlinx-coroutines-reactor'
    implementation 'kotlinx-coroutines-rx1'
    implementation 'kotlinx-coroutines-rx2'
    // integration
    implementation 'kotlinx-coroutines-guava'
    implementation 'kotlinx-coroutines-jdk8'
    implementation 'kotlinx-coroutines-nio'
    implementation 'kotlinx-coroutines-quasar'

    View full-size slide

  54. Coroutine modules
    // retrofit 2
    interface MyService {
    @GET("/user")
    fun getUser(): Deferred
    // or
    @GET("/user")
    fun getUser(): Deferred>
    }

    View full-size slide

  55. Resources
    www.github.com/kotlin/kotlinx.coroutines
    /ui
    /reactive
    /integration
    - documentation
    - guide to UI programming
    - guide to reactive streams
    - guide to future-based libraries

    View full-size slide

  56. googlesamples/android-architecture
    https://github.com/dmytrodanylyk
    /android-architecture/tree
    /todo-mvp-kotlin-coroutines

    View full-size slide

  57. googlesamples/android-architecture

    View full-size slide

  58. Android Coroutines Survey

    View full-size slide

  59. @dmytrodanylyk
    medium.com/@dmytrodanylyk
    speakerdeck.com/dmytrodanylyk

    View full-size slide