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

Async Operations with Kotlin Coroutines

Async Operations with Kotlin Coroutines

Explains how we currently do async operations in Android and how kotlin coroutines helps to write asynchronous code in a synchronous manner.

Adegeye Mayowa

October 05, 2018
Tweet

More Decks by Adegeye Mayowa

Other Decks in Programming

Transcript

  1. fun login(username: String, password: String): User fun getOrders(userId: String): List<Orders>

    fun fetchUserOrders() { val user = login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) }
  2. fun fetchUserOrders(username: String, password: String) { login(username, password) { user

    -> getOrders(user.userId) { orders -> showUserOrders(orders) } } } Callbacks
  3. RxJava Example fun fetchUserOrders(username: String, password: String) { login(username, password)

    .flatMap { user → getOrders(user.userId) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.main()) .subscribe({orders -> showUserOrders(orders)}) }
  4. What we actually want fun fetchUserOrders() { val user =

    login(username, password) val orders = getOrders(user.userId) showUserOrders(orders) }
  5. What is a coroutine A lightweight thread that can run

    a computation without blocking. It can be suspended and resumed at a later time.
  6. SUSPENDING FUNCTION A function that can be started, paused and

    resumed at a later time. • Can only be called from a coroutine and from another suspending function
  7. SUSPENDING FUNCTION A function that can be started, paused and

    resumed at a later time. • Can only be called from a coroutine and from another suspending function • Can take parameters and have return types.
  8. fun doHeavyComputation(param: Int, callback:Continuation<Int>) interface Continuation<in T> { val context:

    CoroutineContext fun resume(value: T) { } fun resumeWithException(exception: Throwable) }
  9. COROUTINE CONTEXT Coroutine Context: A map from CoroutineContext.Key to CoroutineContext.Element

    • Job • ContinuationInterceptor • CoroutineName • CoroutineExceptionHandler
  10. COROUTINE DISPATCHERS Determines which thread or threads the coroutine uses

    for its execution. It can confine a coroutine to specific thread, a thread pool or let it run unconfined.
  11. Launch fun CoroutineScope.launch( context: CoroutineContext = EmptyCoroutineContext, Start: CoroutineStart =

    CoroutineStart.DEFAULT, onCompletion: CompletionHandler? = null, block: suspend CoroutineScope.() -> Unit ): Job
  12. Example suspend fun fibonacciN(n: Int): Int {...} val job =

    GlobalScope.launch { val fib = fibonacciN(20) println(“20th fibonacci is $fib”) }
  13. fun <T> CoroutineScope.async( context: CoroutineContext = EmptyCoroutineContext, start: CoroutineStart =

    CoroutineStart.DEFAULT, onCompletion: CompletionHandler? = null block: suspend CoroutineScope.() -> T ): Deferred<T> Async
  14. Async... • You need to call await() on the returned

    Deferred<T> object to get the eventual result.
  15. Async... • You need to call await() on the returned

    Deferred<T> object to get the actual result. • Async..await be used to run tasks in parallel.
  16. Async... • You need to call await() on the returned

    Deferred<T> object to get the actual result. • Async..await be used to run tasks in parallel. • Deferred<T> is also a job.
  17. Parallel operations example suspend fun loadImage(url: String): Bitmap fun combineImages()=

    GlobalScope.launch(Dispatchers.MAIN) { val image1 = async(Dispatchers.DEFAULT) { loadImage(“..”) } val image2 = async(Dispatchers.DEFAULT) { loadImage(“..”) } imageView.setImageBitmap( combine(image1.await(), image2.await()) ) }
  18. Parallel operations example suspend fun loadImage(url: String): Bitmap fun combineImages()=

    GlobalScope.launch(Dispatchers.MAIN) { val image1 = async(Dispatchers.DEFAULT) { loadImage(“..”) } val image2 = async(Dispatchers.DEFAULT) { loadImage(“..”) } imageView.setImageBitmap( combine(image1.await(), image2.await()) ) }
  19. private fun loadData() = GlobalScope.launch(Dispatchers.MAIN) { view.showLoading() val result =

    withContext(Dispatcher.IO) { dataRepository.loadData() } view.hideLoading() }
  20. private fun loadData() = GlobalScope.launch(Dispatchers.MAIN) { view.showLoading() val result =

    withContext(Dispatcher.IO) { dataRepository.loadData() } view.hideLoading() view.showData(result) }
  21. get user orders revisited fun fetchUserOrders() = GlobalScope.launch(Dispatchers.MAIN){ val user

    = withContext(Dispatchers.IO) { login(username, password) } val orders = withContext(Dispatchers.IO) { getOrders(user.userId) } showUserOrders(orders) }
  22. CANCELLING A JOB Call cancel() on the Job object returned

    from a single coroutine val job = GlobalScope.launch { // background operation } job.cancel()
  23. Child Coroutines inherits the context of parent coroutines val job

    = GlobalScope.launch { val task1 = async {...} val task2 = async {...} val result = task1.await() + task2.await() }
  24. Child Coroutines inherits the context of parent coroutines val job

    = GlobalScope.launch { val task1 = async {...} val task2 = async {...} val result = task1.await() + task2.await() } job.cancel() //cancels the execution of parent and child coroutines
  25. class MainActivity: AppCompatActivity(), CoroutineScope { override val coroutineContext: CoroutineContext get()

    = Dispatchers.DEFAULT private fun loadData() { } private fun doAnimations() { } }
  26. class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override

    val coroutineContext: CoroutineContext get() = Dispatchers.DEFAULT private fun loadData() { } private fun doAnimations() { } }
  27. class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override

    val coroutineContext: CoroutineContext get() = Dispatchers.DEFAULT override fun onCreate(savedInstanceState: Bundle?) { job = Job() } private fun loadData() { } }
  28. class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override

    val coroutineContext: CoroutineContext get() = Dispatchers.DEFAULT override fun onCreate(savedInstanceState: Bundle?) { job = Job() } override fun onDestroy() { job.cancel() } }
  29. class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override

    val coroutineContext: CoroutineContext get() = Dispatchers.DEFAULT + job override fun onCreate(savedInstanceState: Bundle?) { job = Job() } override fun onDestroy() { job.cancel() } }
  30. class MainActivity: AppCompatActivity(), CoroutineScope { private lateint var job override

    val coroutineContext: CoroutineContext get() = Dispatchers.DEFAULT + job override fun onCreate(savedInstanceState: Bundle?) { job = Job() } override fun onDestroy() { job.cancel() } }
  31. HANDLING EXCEPTIONS Exceptions can be handled in the conventional way

    by wrapping code in try..catch block. Coroutine builders either propagate exceptions automatically or expose it to the user
  32. fun main(args: Array<String>) = runBlocking { val job = GlobalScope.launch

    { println("Throwing exception from launch") throw IndexOutOfBoundsException() } job.join() println("Joined failed job") val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() } try { deferred.await() println("Unreached") } catch (e: ArithmeticException) { println("Caught ArithmeticException") } }
  33. fun main(args: Array<String>) = runBlocking { val job = GlobalScope.launch

    { println("Throwing exception from launch") throw IndexOutOfBoundsException() } job.join() println("Joined failed job") val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() } try { deferred.await() println("Unreached") } catch (e: ArithmeticException) { println("Caught ArithmeticException") } }
  34. fun main(args: Array<String>) = runBlocking { val job = GlobalScope.launch

    { println("Throwing exception from launch") throw IndexOutOfBoundsException() // exception printed out to the console } job.join() println("Joined failed job") val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() //nothing happens, waits for user to call await } try { deferred.await() println("Unreached") } catch (e: ArithmeticException) { println("Caught ArithmeticException") } }
  35. fun main(args: Array<String>) = runBlocking { val job = GlobalScope.launch

    { println("Throwing exception from launch") throw IndexOutOfBoundsException() // exception printed out to the console } job.join() println("Joined failed job") val deferred = GlobalScope.async { println("Throwing exception from async") throw ArithmeticException() //nothing happens, waits for user to call await } try { deferred.await() println("Unreached") // will not be printed } catch (e: ArithmeticException) { println("Caught ArithmeticException") } }
  36. CoroutineExceptionHandler val exceptionHandler = CoroutineExceptionHandler { _, exception -> print(“Caught:

    $exception") } runBlocking { val job = GlobalScope.launch(exceptionHandler) { throw IOException(“error occurred) } job.join() } //prints: caught: java.io.IOException: error occurred
  37. • Provides a way to transfer a stream of values

    • They can be shared between different coroutines and have default capacity of 1.
  38. • Provides a way to transfer a stream of values

    • They can be shared between different coroutines and have default capacity of 1. • Channels implements two interfaces, the SendChannel<E> and the ReceiveChannel<E>
  39. public interface SendChannel<E> { suspend fun send(element: E) fun offer(element:

    E) fun close(cause: Throwable? = null) : Boolean } public interface ReceiveChannel<E> { suspend fun receive(): E fun close(throwable? = null): Boolean }
  40. USAGE val channel = Channel<Int>() //default size is 1 GlobalScope.launch

    { for (i in 1..5) { delay(100) channel.send(i) } channel.close() }
  41. USAGE val channel = Channel<Int>() //default size is 1 GlobalScope.launch

    { for (i in 1..5) { delay(100) channel.send(i) } channel.close() }
  42. USAGE val channel = Channel<Int>() //default size is 1 GlobalScope.launch

    { for (i in 1..5) { delay(100) channel.send(i) } channel.close() } GlobalScope.launch { for (i in channel) println(“received: $i”) }
  43. Producer, Publisher and Actor • produce<E> {} - streams of

    event • publish<E> {} - becomes a stream of event upon subscription
  44. Producer, Publisher and Actor • produce<E> {} - streams of

    event • publish<E> {} - becomes a stream of event upon subscription • actor<E> - implements SendChannel<E>
  45. RxJava InterOp Call `openForSubscription` extension function on Observable / Flowable

    Flowable.range(1, 5).openForSubscription().consume { for (i in this) println(i) // ReceiveChannel<Int> }
  46. RxJava InterOp Call `openForSubscription` extension function on Observable / Flowable

    Flowable.range(1, 5).openForSubscription().consume { for (i in this) println(i) // ReceiveChannel<Int> } Or Flowable.range(1, 5).consumeEach{ println($it) }
  47. Convert Job to Rx Completable val job = GlobalScope.launch {

    heavyComputation() } job.asCompletable(Dispatchers.Default)
  48. Convert Job to Rx Completable val job = GlobalScope.launch {

    heavyComputation() } job.asCompletable(Dispatchers.Default).subscribe({..},{.. })
  49. Convert Deferred to Rx Single suspend fun loadData(): List<Item> {

    } val deferred = GlobalScope.async { loadData() } deferred.asSingle(Dispatcher.DEFAULT).subscribe({ //on success }, { //on error })
  50. ANDROID INTEGRATION kotlin { experimental { coroutines ‘enable’ } }

    implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1’
  51. ANDROID INTEGRATION kotlin { experimental { coroutines ‘enable’ } }

    implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1’ implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:0.26.1’
  52. ANDROID INTEGRATION kotlin { experimental { coroutines ‘enable’ } }

    implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-core:0.26.1’ implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-android:0.26.1’ implementation ‘org.jetbrains.kotlinx:kotlinx-coroutines-rx2:0.26.1’
  53. Retrofit Integration val retrofit = Retrofit.Builder() .baseUrl(“http://apiurl.com/”) .addCallAdapterFactory(CoroutineCallAdapterFactory()) .build(); //gradle

    implementation “com.jakewharton.retrofit:retrofit2-kotlin-coroutine-experimental-adapter:1.0.0” implementation “com.jakewharton.retrofit:retrofit2-kotlin-coroutine-adapter:0.9.2”