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.

56d515afd6a1001e8264df0d96c7a80e?s=128

Dmytro Danylyk

March 20, 2018
Tweet

Transcript

  1. Android Coroutine Recipes Dmytro Danylyk Atlassian

  2. Coroutines simplify asynchronous programming by providing possibility to write code

    in direct style (sequentially).
  3. Thread { code } .start() What we have

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

  5. object MyTask: AsyncTask() { override fun doInBackground { code }

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

    override fun onPostExecute { code } } Thread { code } .start() What we have
  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
  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
  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
  10. What we have What we want

  11. What we want fun loadData() = launch { view.showLoading() val

    result = async { // computation }.await() view.showData(result) view.hideLoading() }
  12. Coroutine building blocks

  13. coroutine builder coroutine context suspend function builders to launch coroutine

    coroutine dispatchers mark suspension point
  14. withContext() T returns result uncaught exception crash app

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

    T returns result uncaught exception crash app
  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
  17. CoroutineContext UI dispatch execution into Android main thread

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

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

    execution into Android main thread dispatch execution into current thread
  20. suspend function suspend fun loadData(): Data fun loadData(listener: Continuation<Data>) transforms

    to
  21. Launch

  22. How to launch coroutine // kotlinx.coroutines.experimental.android val uiContext: CoroutineContext =

    UI // kotlinx.coroutines.experimental val bgContext: CoroutineContext = CommonPool
  23. How to launch coroutine private fun loadData() { view.showLoading() val

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

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

    view.showLoading() val result = withContext(bgContext) { dataProvider.provideData() } view.showData(result) view.hideLoading() } child coroutine
  26. 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() }
  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() }
  28. 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() }
  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() }
  30. 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() }
  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() }
  32. 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() }
  33. Cancellation

  34. Сancel a coroutine private var job: Job? = null fun

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

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

    onNextClicked() { job = loadData() } fun onBackClicked() { job?.cancel() } private fun loadData() = launch(uiContext) { // code }
  37. Error handling

  38. 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() }
  39. 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() }
  40. Store inside Deferred var job: Deferred = loadData() job.invokeOnCompletion {

    it: Throwable? -> it?.printStackTrace() }
  41. Exception handler val exceptionHandler= CoroutineExceptionHandler { _, throwable -> throwable.printStackTrace()

    } private fun loadData() = launch(uiContext + exceptionHandler) { // code }
  42. Return null if exception was thrown /** * return T

    or null if exception was thrown */ suspend fun <T> Deferred<T>.awaitSafe(): T? = try { await() } catch (e: Exception) { e.printStackTrace() null }
  43. 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 } }
  44. Return Result<T> /** * return Result<T> which contain either success

    or failure */ suspend fun <T> Deferred<T>.awaitResult(): Result<T> = try { Result(success = await(), failure = null) } catch (e: Exception) { Result(success = null, failure = e) } data class Result<out T>(val success: T?, val failure: Exception?)
  45. Return Result<T> private fun loadData() = launch(uiContext) { val task

    = async(bgContext) { dataProvider.provideData() } val (success, failure) = task.awaitResult() if(success != null) { // success T } else { // failure Exception } }
  46. Testing

  47. Testing: Unconfined context class MainPresenter(val uiContext: CoroutineContext = UI, val

    bgContext: CoroutineContext = CommonPool) { fun loadData() = launch(uiContext) { ... } }
  48. Testing: Unconfined context @Test fun test() { val presenter =

    MainPresenter(Unconfined, Unconfined) // test presenter.loadData() // verify verify(mockView).showLoading() verify(mockDataProvider).provideData() }
  49. Miscellaneous

  50. 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 } }) }
  51. Callback to coroutine suspend fun loadData() = suspendCoroutine<Snapshot> { continuation

    -> val docRef = db.collection("cities").document("NSW") docRef.get().addOnCompleteListener(OnCompleteListener { task -> if (task.isSuccessful) { // task.result.data } else { // task.exception } }) }
  52. Callback to coroutine suspend fun loadData() = suspendCoroutine<Snapshot> { 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) } }) }
  53. Callback to coroutine suspend fun loadData() = suspendCoroutine<Snapshot> { 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) } }) }
  54. 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 }
  55. 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'
  56. Coroutine modules // retrofit 2 interface MyService { @GET("/user") fun

    getUser(): Deferred<User> // or @GET("/user") fun getUser(): Deferred<Response<User>> }
  57. Resources www.github.com/kotlin/kotlinx.coroutines /ui /reactive /integration - documentation - guide to

    UI programming - guide to reactive streams - guide to future-based libraries
  58. googlesamples/android-architecture https://github.com/dmytrodanylyk /android-architecture/tree /todo-mvp-kotlin-coroutines

  59. googlesamples/android-architecture

  60. None
  61. None
  62. None
  63. Android Coroutines Survey

  64. @dmytrodanylyk medium.com/@dmytrodanylyk speakerdeck.com/dmytrodanylyk