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

Droidcon Lisbon: RxJava & Coroutines: A Practical Analysis

Ash Davies
September 10, 2019

Droidcon Lisbon: RxJava & Coroutines: A Practical Analysis

Kotlin has taken the Android world by storm, and is quickly becoming the most popular language of choice. With Coroutines now stable, 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. RxJava has already proven its stability and usefulness, and a comparison between the two frameworks shows that they are simply better suited for different purposes.

In this talk you’ll learn:
- how to 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

September 10, 2019
Tweet

More Decks by Ash Davies

Other Decks in Programming

Transcript

  1. RxJava & Coroutines
    A Practical Analysis
    Droidcon Lisbon
    !
    @askashdavies

    View Slide

  2. @askashdavies

    View Slide

  3. @askashdavies

    View Slide

  4. pusher.com/state-of-kotlin

    View Slide

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

    View Slide

  6. Coroutines
    @askashdavies

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. Coroutine Builders
    @askashdavies

    View Slide

  11. Coroutine Builders
    val deferred: Deferred = async { "Hello World!" }
    @askashdavies

    View Slide

  12. Coroutine Builders
    val deferred: Deferred = async { "Hello World!" }
    val result: String = deferred.await()
    @askashdavies

    View Slide

  13. Coroutine Builders
    val deferred: Deferred = async { "Hello World!" }
    val result: String = deferred.await()
    val job: Job = launch { "Hello World!" }
    @askashdavies

    View Slide

  14. Coroutine Builders
    val deferred: Deferred = async { "Hello World!" }
    val result: String = deferred.await()
    val job: Job = launch { "Hello World!" }
    job.join()
    @askashdavies

    View Slide


  15. Stability
    @askashdavies

    View Slide

  16. @askashdavies

    View Slide

  17. @Annotations
    (
    !
    Here be dragons)
    @askashdavies

    View Slide

  18. Annotations
    @ExperimentalCoroutinesApi //
    @askashdavies

    View Slide

  19. Annotations
    @ExperimentalCoroutinesApi //
    @FlowPreview //

    @askashdavies

    View Slide

  20. Annotations
    @ExperimentalCoroutinesApi //
    @FlowPreview //

    @ObsoleteCoroutinesApi //

    @askashdavies

    View Slide

  21. Annotations
    @ExperimentalCoroutinesApi //
    @FlowPreview //

    @ObsoleteCoroutinesApi //

    @InternalCoroutinesApi //

    @askashdavies

    View Slide

  22. Annotations
    @Experimental
    @askashdavies

    View Slide

  23. !
    Coroutines
    @askashdavies

    View Slide

  24. !
    Native first-party
    library
    @askashdavies

    View Slide

  25. !
    Easy-to-use
    @askashdavies

    View Slide

  26. !
    suspend fun
    @askashdavies

    View Slide

  27. Dispatchers
    » Default
    » IO
    » Main
    » Android (Main Thread Dispatcher)
    » JavaFx (Application Thread Dispatcher)
    » Swing (Event Dispatcher Thread)
    » Unconfined
    @askashdavies

    View Slide

  28. !
    History of Android
    @askashdavies

    View Slide

  29. Background Processes
    @askashdavies

    View Slide

  30. Background Processes
    !
    Runnable / Handler
    @askashdavies

    View Slide

  31. Background Processes
    !
    AsyncTask
    @askashdavies

    View Slide

  32. Background Processes
    !
    IntentService
    @askashdavies

    View Slide

  33. Background Processes
    !
    Loader
    @askashdavies

    View Slide

  34. Background Processes
    WorkManager
    @askashdavies

    View Slide

  35. !
    @askashdavies

    View Slide

  36. xkcd.com/927/

    View Slide

  37. RxJava to the
    rescue
    @askashdavies

    View Slide


  38. Chained operations
    @askashdavies

    View Slide

  39. ⬆ ⬇
    Abstract threading
    @askashdavies

    View Slide

  40. !
    Reactive
    @askashdavies

    View Slide

  41. @askashdavies

    View Slide

  42. @askashdavies

    View Slide

  43. @askashdavies

    View Slide

  44. @askashdavies

    View Slide

  45. @askashdavies

    View Slide

  46. !
    Asynchronous APIs
    Synchronous APIs
    @askashdavies

    View Slide

  47. @askashdavies

    View Slide

  48. !
    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

  49. Anchorman: The Legend of Ron Burgundy (DreamWorks Pictures)

    View Slide


  50. Hidden complexity
    @askashdavies

    View Slide

  51. !
    Hidden gotchas
    @askashdavies

    View Slide

  52. !
    Memory footprint
    @askashdavies

    View Slide


  53. Steep learning curve
    @askashdavies

    View Slide

  54. @askashdavies

    View Slide

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

    View Slide

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

    View Slide

  57. Coroutine Use Cases
    @askashdavies

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  61. Thread Handling
    // RxJava2
    getUser()
    .subscribeOn(Schedulers.io())
    .observeOn(AndroidSchedulers.mainThread())
    .subscribe { /* Do something */ }
    // Coroutines
    launch(Dispatchers.Main) {
    withContext(Dispatchers.IO) {
    val user = getUser()
    /* Do something */
    }
    }
    @askashdavies

    View Slide

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

    View Slide

  63. 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)
    })
    }
    // Coroutines
    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

  64. 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

  65. !
    Backpressure
    @askashdavies

    View Slide

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

    View Slide

  67. ViewModel.viewModelScope
    androidx.lifecycle:2.1.0
    @askashdavies

    View Slide

  68. Channels
    @askashdavies

    View Slide

  69. Roman Elizarov
    Cold flows, hot
    channels
    flow {
    emit("Hello")
    emit("World")
    }
    medium.com/@elizarov/cold-flows-hot-channels-d74769805f9

    View Slide

  70. Flow
    !
    Observable.create { emitter: ObservableEmitter ->
    emitter.onNext("Hello")
    emitter.onNext("World")
    }
    @askashdavies

    View Slide

  71. Flow
    !
    flow { collector: FlowCollector ->
    collector.emit("Hello")
    collector.emit("World")
    }
    @askashdavies

    View Slide

  72. Flow
    !
    flow { emit("Hello world") }
    .onEach { delay(500) }
    .map { it.length }
    .collect { println("Length: $it") }
    @askashdavies

    View Slide

  73. Testing
    @Test
    fun testFoo() = runBlockingTest {
    val actual = foo()
    // ...
    }
    suspend fun foo() {
    delay(1_000)
    // ...
    }
    github.com/Kotlin/kotlinx.coroutines/tree/master/kotlinx-coroutines-test

    View Slide

  74. runBlockingTest
    » Auto-advancing of time
    ⏱⏩
    » Explicit time control
    #
    » Eager execution of launch or async
    » Pause, manually advance, and restart execution
    » Report uncaught exceptions as test failures
    @askashdavies

    View Slide


  75. RxJava: Complex Business Logic
    @askashdavies

    View Slide

  76. Asynchronicity Comparison
    Manuel Vivo (@manuelvicnt)
    medium.com/capital-one-tech/coroutines-and-rxjava-an-asynchronicity-comparison-part-1-asynchronous-programming-e726a925342a

    View Slide

  77. Is Coroutines a replacement for RxJava?
    @askashdavies

    View Slide

  78. Maybe...
    @askashdavies

    View Slide

  79. Should I migrate to Coroutines?
    @askashdavies

    View Slide

  80. Probably not...
    @askashdavies

    View Slide

  81. "If it ain't broke, don't fix it"
    — Bert Lance, Nation's Business, 1977
    @askashdavies

    View Slide

  82. © 2019 Viacom International Inc.

    View Slide

  83. Did "you" migrate to Coroutines?
    @askashdavies

    View Slide

  84. @askashdavies

    View Slide

  85. How could I migrate to Coroutines?
    @askashdavies

    View Slide

  86. !
    Migration Policy
    @askashdavies

    View Slide

  87. Retrofit Services
    @askashdavies

    View Slide

  88. Retrofit 2.6.0
    Built-in suspend support
    github.com/square/retrofit/blob/master/CHANGELOG.md#version-260-2019-06-05

    View Slide

  89. interface UserService {
    @GET("/user")
    suspend fun getUser(): User
    }
    GlobalScope.launch {
    val user = retrofit
    .create() // >= 2.5.0
    .getUser()
    }
    @askashdavies

    View Slide

  90. Coroutines RxJava2
    org.jetbrains.kotlinx:kotlinx-coroutines-rx2:+
    @askashdavies

    View Slide

  91. 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

  92. val service: UserService = retrofit.create() // >= 2.5.0
    GlobalScope
    .rxSingle { service.getUser() }
    .observeOn(AndroidSchedulers.mainThread())
    .subscribeOn(Schedulers.io())
    .subscribe(
    { /* Do something with user */ },
    { /* Handle error ... probably */ }
    )
    @askashdavies

    View Slide

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

    View Slide

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

    View Slide

  95. Observable.await...
    val observable = Observable.just(1, 2, 3, 4)
    // Await first item
    val item = observable.awaitFirst()
    // Print each item
    observable.consumeEach(::println)
    // Consume all items
    observable
    .openSubscription()
    .consume {
    println(it.size)
    }
    kotlin.github.io/kotlinx.coroutines/kotlinx-coroutines-rx2/kotlinx.coroutines.rx2/io.reactivex.-observable-source/index.html

    View Slide

  96. Exceptions
    @askashdavies

    View Slide

  97. !
    Conclusion
    @askashdavies

    View Slide

  98. Cheers!
    !
    @askashdavies

    View Slide