Slide 1

Slide 1 text

Android Coroutine Recipes Dmytro Danylyk Atlassian

Slide 2

Slide 2 text

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

Slide 3

Slide 3 text

Thread { code } .start() What we have

Slide 4

Slide 4 text

Thread { code } .start() What we have

Slide 5

Slide 5 text

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

Slide 6

Slide 6 text

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

Slide 7

Slide 7 text

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

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

What we have What we want

Slide 11

Slide 11 text

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

Slide 12

Slide 12 text

Coroutine building blocks

Slide 13

Slide 13 text

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

Slide 14

Slide 14 text

withContext() T returns result uncaught exception crash app

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

CoroutineContext UI dispatch execution into Android main thread

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

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

Slide 20

Slide 20 text

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

Slide 21

Slide 21 text

Launch

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

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

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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() }

Slide 27

Slide 27 text

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() }

Slide 28

Slide 28 text

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() }

Slide 29

Slide 29 text

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() }

Slide 30

Slide 30 text

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() }

Slide 31

Slide 31 text

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() }

Slide 32

Slide 32 text

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() }

Slide 33

Slide 33 text

Cancellation

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

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

Slide 37

Slide 37 text

Error handling

Slide 38

Slide 38 text

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() }

Slide 39

Slide 39 text

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() }

Slide 40

Slide 40 text

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

Slide 41

Slide 41 text

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

Slide 42

Slide 42 text

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 }

Slide 43

Slide 43 text

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 } }

Slide 44

Slide 44 text

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?)

Slide 45

Slide 45 text

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 } }

Slide 46

Slide 46 text

Testing

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

Miscellaneous

Slide 50

Slide 50 text

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 } }) }

Slide 51

Slide 51 text

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 } }) }

Slide 52

Slide 52 text

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) } }) }

Slide 53

Slide 53 text

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) } }) }

Slide 54

Slide 54 text

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 }

Slide 55

Slide 55 text

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'

Slide 56

Slide 56 text

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

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

googlesamples/android-architecture

Slide 60

Slide 60 text

No content

Slide 61

Slide 61 text

No content

Slide 62

Slide 62 text

No content

Slide 63

Slide 63 text

Android Coroutines Survey

Slide 64

Slide 64 text

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