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

Berlindroid: RxJava & Coroutines: A Practical Analysis

Ash Davies
January 30, 2019

Berlindroid: RxJava & Coroutines: A Practical Analysis

Kotlin has taken the Android world by storm, and is quickly becoming the most popular language, with coroutines approaching stability, does it make sense to replace your RxJava implementations for Coroutines?

Despite the hype, it may not make sense to jump on the bandwagon just yet, with RxJava having already proven its stability and usefulness, and in many cases the comparison between the two frameworks showing that they simply fit different purposes.

In this talk, you can learn how you can utilise the strengths of each framework, how to correctly choose the best solution for the requirements of your project, whether it may be beneficial to migrate or run them concurrently, and how you can start doing so for your project.

Ash Davies

January 30, 2019
Tweet

More Decks by Ash Davies

Other Decks in Programming

Transcript

  1. RxJava & Coroutines
    A Practical Analysis
    @askashdavies

    View Slide

  2. @askashdavies

    View Slide

  3. @askashdavies

    View Slide

  4. !
    @askashdavies

    View Slide

  5. Adoption
    @askashdavies

    View Slide

  6. 7.7% (2015)
    @askashdavies

    View Slide

  7. 19.5% (2016)
    7.7% (2015)
    @askashdavies

    View Slide

  8. 46.8% (2017)
    19.5% (2016)
    7.7% (2015)
    @askashdavies

    View Slide

  9. blog.jetbrains.com/kotlin/2016/02/kotlin-1-0-released-pragmatic-language-for-jvm-and-android/

    View Slide

  10. Multi Platform
    @askashdavies

    View Slide

  11. Coroutines
    @askashdavies

    View Slide

  12. fun main() {
    GlobalScope.launch {
    delay(1000L)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(2000L)
    }
    @askashdavies

    View Slide

  13. fun main() {
    GlobalScope.launch {
    delay(1000L)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(2000L)
    }
    @askashdavies

    View Slide

  14. fun main() {
    GlobalScope.launch {
    delay(1000L)
    println("World!")
    }
    println("Hello,")
    Thread.sleep(2000L)
    }
    @askashdavies

    View Slide


  15. Stability
    @askashdavies

    View Slide

  16. @askashdavies

    View Slide

  17. async / launch
    @askashdavies

    View Slide

  18. val deferred: Deferred = async { "Hello" }
    @askashdavies

    View Slide

  19. val result: String = deferred.await()
    @askashdavies

    View Slide

  20. val job: Job = launch { "Hello" }
    @askashdavies

    View Slide

  21. job.join()
    @askashdavies

    View Slide

  22. @Annotations
    bit.ly/2BrxgKv
    @askashdavies

    View Slide

  23. !
    Here be dragons
    @askashdavies

    View Slide


  24. @ExperimentalCoroutinesApi
    @askashdavies

    View Slide


  25. @ObsoleteCoroutinesApi
    @askashdavies

    View Slide


  26. @InternalCoroutinesApi
    @askashdavies

    View Slide

  27. @askashdavies

    View Slide

  28. @askashdavies

    View Slide

  29. Coroutines
    !
    Best thing since sliced bread
    @askashdavies

    View Slide

  30. Coroutines
    Best thing since ... brotchen?
    @askashdavies

    View Slide

  31. !
    Coroutines
    @askashdavies

    View Slide

  32. !
    Native first-party library
    @askashdavies

    View Slide

  33. !
    Easy-to-use
    @askashdavies

    View Slide

  34. !
    suspend fun
    @askashdavies

    View Slide

  35. Dispatchers.Main
    @askashdavies

    View Slide

  36. !
    History of Android
    @askashdavies

    View Slide

  37. Background Processes
    @askashdavies

    View Slide

  38. Background Processes
    !
    Runnable / Handler
    @askashdavies

    View Slide

  39. Background Processes
    !
    AsyncTask
    @askashdavies

    View Slide

  40. Background Processes
    !
    IntentService
    @askashdavies

    View Slide

  41. Background Processes
    !
    Loader
    @askashdavies

    View Slide

  42. Background Processes
    WorkManager
    @askashdavies

    View Slide

  43. Background Processes
    AlarmManager
    @askashdavies

    View Slide

  44. Background Processes
    AlarmManager, CountDownTimer
    @askashdavies

    View Slide

  45. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask
    @askashdavies

    View Slide

  46. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager
    @askashdavies

    View Slide

  47. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager,
    HandlerThread
    @askashdavies

    View Slide

  48. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager,
    HandlerThread, JobDispatcher
    @askashdavies

    View Slide

  49. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager,
    HandlerThread, JobDispatcher,
    JobScheduler
    @askashdavies

    View Slide

  50. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager,
    HandlerThread, JobDispatcher,
    JobScheduler,
    ScheduledThreadPoolExecutor
    @askashdavies

    View Slide

  51. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager,
    HandlerThread, JobDispatcher,
    JobScheduler,
    ScheduledThreadPoolExecutor, Timer
    @askashdavies

    View Slide

  52. Background Processes
    AlarmManager, CountDownTimer ,
    FutureTask, GcmNetworkManager,
    HandlerThread, JobDispatcher,
    JobScheduler,
    ScheduledThreadPoolExecutor, Timer,
    Task
    @askashdavies

    View Slide

  53. !
    @askashdavies

    View Slide

  54. xkcd.com/927/

    View Slide

  55. RxJava to the rescue
    @askashdavies

    View Slide


  56. Chained operations
    @askashdavies

    View Slide

  57. ⬆ ⬇
    Abstracted threading
    @askashdavies

    View Slide

  58. !
    Reactive programming
    @askashdavies

    View Slide

  59. !
    @askashdavies

    View Slide

  60. @askashdavies

    View Slide

  61. @askashdavies

    View Slide

  62. @askashdavies

    View Slide

  63. @askashdavies

    View Slide

  64. @askashdavies

    View Slide

  65. @askashdavies

    View Slide

  66. !
    Asynchronous API's
    @askashdavies

    View Slide

  67. !
    Synchronous API's
    @askashdavies

    View Slide

  68. Observable.fromIterable()
    @askashdavies

    View Slide

  69. Flowable / Observable
    Single / Completable / Maybe
    @askashdavies

    View Slide

  70. @askashdavies

    View Slide

  71. !
    Observable
    .fromIterable(resourceDraft.getResources())
    .flatMap(resourceServiceApiClient::createUploadContainer)
    .zipWith(Observable.fromIterable(resourceDraft.getResources()), Pair::create)
    .flatMap(uploadResources())
    .toList()
    .toObservable()
    .flatMapMaybe(resourceCache.getResourceCachedItem())
    .defaultIfEmpty(Resource.getDefaultItem())
    .flatMap(postResource(resourceId, resourceDraft.getText(), currentUser, resourceDraft.getIntent()))
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io())
    .subscribe(
    resource -> repository.setResource(resourceId, resource, provisionalResourceId),
    resourceUploadError(resourceId, resourceDraft, provisionalResourceId)
    );
    @askashdavies

    View Slide

  72. @askashdavies

    View Slide


  73. Hidden complexity
    @askashdavies

    View Slide

  74. !
    Hidden gotchas
    @askashdavies

    View Slide

  75. !
    Memory footprint
    @askashdavies

    View Slide


  76. Steep learning curve
    @askashdavies

    View Slide

  77. explosm.net/comics/3185/

    View Slide

  78. @askashdavies

    View Slide

  79. #RxMustDie
    pca.st/7IJG
    @askashdavies

    View Slide

  80. "If all you have is a hammer,
    everything looks like a nail"
    — Abraham Maslow, The Psychology of Science, 1966
    @askashdavies

    View Slide

  81. Reactive / Imperative
    @askashdavies

    View Slide

  82. Coroutine Use Cases
    @askashdavies

    View Slide

  83. Network Call Handling
    // RxJava2
    fun getUser(): Single = Single.fromCallable { /* ... */ }
    // Coroutine
    suspend fun getUser(): User = /* ... */
    @askashdavies

    View Slide

  84. Cache Retrieval
    // RxJava2
    fun getUser(): Maybe = Maybe.fromCallable { /* ... */ }
    // Coroutine
    suspend fun getUser(): User? = /* ... */
    @askashdavies

    View Slide

  85. Background Task Handling
    // RxJava2
    fun storeUser(user: User): Completable.fromCallable { /* ... */ }
    // Coroutine
    suspend fun storeUser(user: User) { /* ... */ }
    @askashdavies

    View Slide

  86. RxJava2 / Coroutines
    Single / T
    Maybe / T?
    Completable / Unit
    @askashdavies

    View Slide

  87. Thread Handling
    // RxJava2
    getUser()
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { /* Do something */ }
    // Coroutine
    launch(Dispatchers.Main) {
    val user = getUser()
    /* Do something */
    }
    @askashdavies

    View Slide

  88. !
    FlatMap
    // RxJava2
    getUser()
    .flatMap { doSomethingWithUser(it) }
    .subscribeOn(Schedulers.computation())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { /* Do something else */ }
    // Coroutine
    launch(Dispatchers.Main) {
    val user = getUser()
    val smth = doSomethingWithUser(user)
    /* Do something else */
    }
    @askashdavies

    View Slide

  89. Callback Consumption
    // RxJava2
    fun getSingle(): Single = Single.create { emitter ->
    doSomethingAsync(object: Callback {
    override fun onComplete(result: T) = emitter.onSuccess(result)
    override fun onException(exception: Exception) = emitter.onError(exception)
    })
    }
    // Coroutine
    suspend fun getCoroutine() : T = suspendCoroutine { continuation ->
    doSomethingAsync(object : Callback {
    override fun onComplete(result: T) = continuation.resume(result)
    override fun onException(exception: Exception) = continuation.resumeWithException(exception)
    })
    }
    @askashdavies

    View Slide

  90. !
    Continuation
    @askashdavies

    View Slide

  91. Task Cancellation
    // RxJava2
    val disposable: Disposable = Single
    .create { /* Do something */ }
    .subscribe { /* Do something else */ }
    disposable.dispose()
    // Coroutine
    val parent: Job = Job()
    launch(Dispatchers.Main + parent) { /* Do something */ }
    parent.cancelChildren()
    @askashdavies

    View Slide

  92. ViewModel.viewModelScope
    androidx.lifecycle:2.1.0-alpha01
    bit.ly/2FZ0FPN
    @askashdavies

    View Slide

  93. Lifecycle Task Cancellation
    class MainViewModel : ViewModel {
    fun init() {
    viewModelScope.launch {
    someSuspendFunction()
    }
    }
    }
    @askashdavies

    View Slide

  94. Value Streams
    // RxJava2
    val publisher = PublishSubject()
    publisher.subscribe { /* Do something */ }
    publisher.onNext("Hello")
    // Coroutine
    val channel = Channel()
    launch { channel.send("Hello") }
    launch { channel.consumeEach { /* Do something */ } }
    @askashdavies

    View Slide

  95. !
    Backpressure
    @askashdavies

    View Slide

  96. !
    Backpressure
    launch { channel.send("Hello") }
    @askashdavies

    View Slide

  97. !
    RxJava
    @askashdavies

    View Slide

  98. Channels
    bit.ly/2DQU7lb
    @askashdavies

    View Slide


  99. Transformations
    @askashdavies

    View Slide


  100. Cold Streams
    @askashdavies

    View Slide


  101. Complex Business Logic
    @askashdavies

    View Slide

  102. Asynchronicity Comparison
    Manuel Vivo (@manuelvicnt)
    bit.ly/2R27stP
    @askashdavies

    View Slide

  103. Is Coroutines a replacement for RxJava?
    @askashdavies

    View Slide

  104. Maybe...
    @askashdavies

    View Slide

  105. Should I migrate to Coroutines?
    @askashdavies

    View Slide

  106. Probably not...
    @askashdavies

    View Slide

  107. "Don't fix what ain't broke"
    — Some Guy
    @askashdavies

    View Slide

  108. © 2019 Viacom International Inc.

    View Slide

  109. Did you migrate to Coroutines?
    @askashdavies

    View Slide

  110. !
    Yes!
    @askashdavies

    View Slide

  111. How could I migrate to Coroutines?
    @askashdavies

    View Slide

  112. !
    Migration Policy
    @askashdavies

    View Slide

  113. Retrofit Services
    @askashdavies

    View Slide

  114. Retrofit2 Coroutines Adapter bit.ly/2TyGXOh
    com.jakewharton.retrofit:retrofit2-kotlin-coroutines-adapter:+
    @askashdavies

    View Slide

  115. interface UserService {
    @GET("/user")
    fun getUser(): Deferred
    }
    val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .build()
    GlobalScope.launch {
    val user = retrofit
    .create() // >= 2.5.0
    .getUser()
    .await()
    }
    @askashdavies

    View Slide

  116. interface UserService {
    @GET("/user")
    fun getUser(): Deferred
    }
    val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .build()
    GlobalScope.launch {
    val user = retrofit
    .create() // >= 2.5.0
    .getUser()
    .await()
    }
    @askashdavies

    View Slide

  117. interface UserService {
    @GET("/user")
    fun getUser(): Deferred
    }
    val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .build()
    GlobalScope.launch {
    val user = retrofit
    .create() // >= 2.5.0
    .getUser()
    .await()
    }
    @askashdavies

    View Slide

  118. interface UserService {
    @GET("/user")
    fun getUser(): Deferred
    }
    val retrofit = Retrofit.Builder()
    .baseUrl("https://example.com/")
    .addCallAdapterFactory(CoroutineCallAdapterFactory())
    .build()
    GlobalScope.launch {
    val user = retrofit
    .create() // >= 2.5.0
    .getUser()
    .await()
    }
    @askashdavies

    View Slide

  119. Retrofit bit.ly/2CU0LUK
    First-party "suspend" support
    @askashdavies

    View Slide

  120. Coroutines RxJava2 bit.ly/2DQ2ZYn
    org.jetbrains.kotlinx:kotlinx-coroutines-rx2:+
    @askashdavies

    View Slide

  121. Name Scope Description
    rxCompletable CoroutineScope Cold completable that starts
    coroutine on subscribe
    rxMaybe CoroutineScope Cold maybe that starts
    coroutine on subscribe
    rxSingle CoroutineScope Cold single that starts
    coroutine on subscribe
    rxObservable ProducerScope Cold observable that starts
    coroutine on subscribe
    rxFlowable ProducerScope Cold observable that starts
    coroutine on subscribe with
    backpressure support
    @askashdavies

    View Slide

  122. val service: UserService = /* ... */
    GlobalScope.rxSingle { service.getUser() }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io())
    .subscribe(
    { /* Do something with user */ },
    { /* Handle error ... maybe */ }
    )
    @askashdavies

    View Slide

  123. Completable.await()
    GlobalScope.launch {
    Completable
    .complete()
    .await()
    }
    @askashdavies

    View Slide

  124. Maybe.await()
    GlobalScope.launch {
    val result: String? = Maybe
    .fromCallable { null as String? }
    .await()
    }
    @askashdavies

    View Slide

  125. Observable.await... bit.ly/2Tl37TK
    GlobalScope.launch {
    val observable = Observable.just(1, 2, 3, 4)
    // Await first item
    val item = observable.awaitFirst()
    // Print each item
    observable.consumeEach {
    println(it)
    }
    // Consume all items
    observable
    .openSubscription()
    .consume {
    println(it.size)
    }
    }
    @askashdavies

    View Slide

  126. !
    Conclusion
    @askashdavies

    View Slide

  127. @askashdavies

    View Slide

  128. Cheers!
    !
    @askashdavies

    View Slide

  129. @askashdavies

    View Slide