Droidcon Lisbon: RxJava & Coroutines: A Practical Analysis

Fc78fd09b8fee61efd4ef003fe104eb6?s=47 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

Fc78fd09b8fee61efd4ef003fe104eb6?s=128

Ash Davies

September 10, 2019
Tweet

Transcript

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

  2. @askashdavies

  3. @askashdavies

  4. pusher.com/state-of-kotlin

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

  6. Coroutines @askashdavies

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

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

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

    } // Hello, // World! @askashdavies
  10. Coroutine Builders @askashdavies

  11. Coroutine Builders val deferred: Deferred<String> = async { "Hello World!"

    } @askashdavies
  12. Coroutine Builders val deferred: Deferred<String> = async { "Hello World!"

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

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

    } val result: String = deferred.await() val job: Job = launch { "Hello World!" } job.join() @askashdavies
  15. ⚖ Stability @askashdavies

  16. @askashdavies

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

  18. Annotations @ExperimentalCoroutinesApi // @askashdavies

  19. Annotations @ExperimentalCoroutinesApi // @FlowPreview // ⚠ @askashdavies

  20. Annotations @ExperimentalCoroutinesApi // @FlowPreview // ⚠ @ObsoleteCoroutinesApi // ⚠ @askashdavies

  21. Annotations @ExperimentalCoroutinesApi // @FlowPreview // ⚠ @ObsoleteCoroutinesApi // ⚠ @InternalCoroutinesApi

    // ☠ @askashdavies
  22. Annotations @Experimental @askashdavies

  23. ! Coroutines @askashdavies

  24. ! Native first-party library @askashdavies

  25. ! Easy-to-use @askashdavies

  26. ! suspend fun @askashdavies

  27. Dispatchers » Default » IO » Main » Android (Main

    Thread Dispatcher) » JavaFx (Application Thread Dispatcher) » Swing (Event Dispatcher Thread) » Unconfined @askashdavies
  28. ! History of Android @askashdavies

  29. Background Processes @askashdavies

  30. Background Processes ! Runnable / Handler @askashdavies

  31. Background Processes ! AsyncTask @askashdavies

  32. Background Processes ! IntentService @askashdavies

  33. Background Processes ! Loader<T> @askashdavies

  34. Background Processes WorkManager @askashdavies

  35. ! @askashdavies

  36. xkcd.com/927/

  37. RxJava to the rescue @askashdavies

  38. ⛓ Chained operations @askashdavies

  39. ⬆ ⬇ Abstract threading @askashdavies

  40. ! Reactive @askashdavies

  41. @askashdavies

  42. @askashdavies

  43. @askashdavies

  44. @askashdavies

  45. @askashdavies

  46. ! Asynchronous APIs Synchronous APIs @askashdavies

  47. @askashdavies

  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
  49. Anchorman: The Legend of Ron Burgundy (DreamWorks Pictures)

  50. ⚙ Hidden complexity @askashdavies

  51. ! Hidden gotchas @askashdavies

  52. ! Memory footprint @askashdavies

  53. ⤴ Steep learning curve @askashdavies

  54. @askashdavies

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

  56. "If all you have is a hammer, everything looks like

    a nail" — Abraham Maslow, The Psychology of Science, 1966 @askashdavies
  57. Coroutine Use Cases @askashdavies

  58. Network Call Handling // RxJava2: Single<T> fun getUser(): Single<User> =

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

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

    { /* ... */ } // Coroutines: Unit suspend fun storeUser(user: User) { /* ... */ } @askashdavies
  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
  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
  63. Callback Consumption // RxJava2 fun getSingle(): Single = Single.create<T> {

    emitter -> doSomethingAsync(object: Callback<T> { 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<T> { override fun onComplete(result: T) = continuation.resume(result) override fun onException(exception: Exception) = continuation.resumeWithException(exception) }) } @askashdavies
  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
  65. ! Backpressure @askashdavies

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

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

  68. Channels @askashdavies

  69. Roman Elizarov Cold flows, hot channels flow { emit("Hello") emit("World")

    } medium.com/@elizarov/cold-flows-hot-channels-d74769805f9
  70. Flow ! Observable.create { emitter: ObservableEmitter<String> -> emitter.onNext("Hello") emitter.onNext("World") }

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

    @askashdavies
  72. Flow ! flow { emit("Hello world") } .onEach { delay(500)

    } .map { it.length } .collect { println("Length: $it") } @askashdavies
  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
  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
  75. ⚙ RxJava: Complex Business Logic @askashdavies

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

  77. Is Coroutines a replacement for RxJava? @askashdavies

  78. Maybe... @askashdavies

  79. Should I migrate to Coroutines? @askashdavies

  80. Probably not... @askashdavies

  81. "If it ain't broke, don't fix it" — Bert Lance,

    Nation's Business, 1977 @askashdavies
  82. © 2019 Viacom International Inc.

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

  84. @askashdavies

  85. How could I migrate to Coroutines? @askashdavies

  86. ! Migration Policy @askashdavies

  87. Retrofit Services @askashdavies

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

  89. interface UserService { @GET("/user") suspend fun getUser(): User } GlobalScope.launch

    { val user = retrofit .create<UserService>() // >= 2.5.0 .getUser() } @askashdavies
  90. Coroutines RxJava2 org.jetbrains.kotlinx:kotlinx-coroutines-rx2:+ @askashdavies

  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
  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
  93. Completable.await() GlobalScope.launch { Completable .complete() .await() } @askashdavies

  94. Maybe.await() GlobalScope.launch { val result: String? = Maybe .fromCallable {

    null as String? } .await() // result == null } @askashdavies
  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
  96. Exceptions @askashdavies

  97. ! Conclusion @askashdavies

  98. Cheers! ! @askashdavies