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

Kotlin Night Berlin: RxJava & Coroutines: A Practical Analysis

Ash Davies
November 22, 2018

Kotlin Night Berlin: 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

November 22, 2018
Tweet

More Decks by Ash Davies

Other Decks in Programming

Transcript

  1. RxJava & Coroutines: A
    Practical Analysis
    ashdavies.io - @askashdavies

    View Slide

  2. View Slide

  3. View Slide

  4. !

    View Slide

  5. Adoption

    View Slide

  6. 7.7% (2015)

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  10. Multi Platform

    View Slide

  11. Coroutines

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide


  15. Stability

    View Slide

  16. async / launch

    View Slide

  17. val deferred: Deferred = async { "Hello" }

    View Slide

  18. val result: String = deferred.await()

    View Slide

  19. val job: Job = launch { "Hello" }

    View Slide

  20. job.join()

    View Slide

  21. Annotations
    bit.ly/2BrxgKv

    View Slide

  22. !
    Here be dragons

    View Slide


  23. @ExperimentalCoroutinesApi

    View Slide


  24. @ObsoleteCoroutinesApi

    View Slide


  25. @InternalCoroutinesApi

    View Slide

  26. View Slide

  27. Coroutines
    !
    Best thing since sliced bread

    View Slide

  28. !
    Coroutines

    View Slide

  29. !
    Native first-party library

    View Slide

  30. !
    Easy-to-use

    View Slide

  31. !
    suspend fun

    View Slide

  32. Dispatchers.Main

    View Slide

  33. !
    History of Android

    View Slide

  34. Background Processes

    View Slide

  35. !
    Runnable
    /
    Handler

    View Slide

  36. !
    AsyncTask

    View Slide

  37. !
    IntentService

    View Slide

  38. !
    Loader

    View Slide

  39. WorkManager

    View Slide

  40. Background Processes
    AlarmManager
    ,
    AsyncTask
    ,
    CountDownTimer
    ,
    FutureTask
    ,
    GcmNetworkManager
    ,
    Handler
    ,
    HandlerThread
    ,
    IntentService
    ,
    JobDispatcher
    ,
    JobScheduler
    ,
    Loader
    ,
    ScheduledThreadPoolExecutor
    ,
    Timer
    ,
    Task
    ,
    WorkManager

    View Slide

  41. https://xkcd.com/927/

    View Slide

  42. RxJava to the rescue

    View Slide


  43. Chained operations

    View Slide

  44. ⬆ ⬇
    Abstracted threading

    View Slide

  45. !
    Reactive programming

    View Slide

  46. !

    View Slide

  47. View Slide

  48. View Slide

  49. View Slide

  50. View Slide

  51. View Slide

  52. View Slide

  53. !
    Asynchronous API's

    View Slide

  54. !
    Synchronous API's

    View Slide

  55. Observable.fromIterable()

    View Slide

  56. Flowable
    /
    Observable
    /
    Single
    /
    Completable
    /
    Maybe

    View Slide

  57. View Slide

  58. !
    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)
    );

    View Slide

  59. View Slide


  60. Hidden complexity

    View Slide

  61. !
    Hidden gotchas

    View Slide

  62. !
    Memory footprint

    View Slide


  63. Steep learning curve

    View Slide

  64. http://explosm.net/comics/3185/

    View Slide

  65. View Slide

  66. #RxMustDie
    pca.st/7IJG

    View Slide

  67. "When all you have is a hammer, everything looks like a nail"
    Ivan Morgillo (@hamen)

    View Slide

  68. Reactive & Imperative programming

    View Slide

  69. Coroutine Use Cases

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  73. RxJava2 / Coroutines
    Single / T
    Maybe / T?
    Completable / Unit

    View Slide

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

    View Slide

  75. !
    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 */
    }

    View Slide

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

    View Slide

  77. 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()

    View Slide

  78. Lifecycle Task Cancellation
    bit.ly/2POmNBJ

    View Slide

  79. Lifecycle Task Cancellation
    class MainActivity : AppCompatActivity {
    override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    lifecycle.coroutineScope.launch {
    someSuspendFunction()
    someOtherSuspendFunction()
    someCancellableSuspendFunction()
    }
    }
    }

    View Slide

  80. 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 */ } }

    View Slide

  81. !
    Backpressure

    View Slide

  82. Channels
    bit.ly/2DQU7lb

    View Slide

  83. RxJava Use Cases

    View Slide


  84. Channels

    View Slide


  85. Cold Streams

    View Slide


  86. Complex Business Logic

    View Slide

  87. Coroutines & RxJava: An Asynchronicity Comparison
    Manuel Vivo (@manuelvicnt)
    bit.ly/2R27stP

    View Slide

  88. Is Coroutines a replacement for RxJava?

    View Slide

  89. Maybe...

    View Slide

  90. Should I migrate to Coroutines?

    View Slide

  91. Probably not...

    View Slide

  92. "Don't fix what ain't broke"
    Some Guy

    View Slide

  93. View Slide

  94. Did "I" migrate to Coroutines?

    View Slide

  95. !
    Yes!

    View Slide

  96. How could I migrate to Coroutines?

    View Slide

  97. !
    Migration Policy

    View Slide

  98. Retrofit Services

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  105. Name Result Scope Description
    [rxCompletable] Completable [CoroutineScope] Cold completable that starts
    coroutine on subscribe
    [rxMaybe] Maybe [CoroutineScope] Cold maybe that starts
    coroutine on subscribe
    [rxSingle] Single [CoroutineScope] Cold single that starts
    coroutine on subscribe
    [rxObservable] Observable [ProducerScope] Cold observable that starts
    coroutine on subscribe
    [rxFlowable] Flowable [ProducerScope] Cold observable that starts
    coroutine on subscribe with
    backpressure support

    View Slide

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

    View Slide

  107. !
    Conclusion

    View Slide

  108. View Slide

  109. Cheers!
    !
    ashdavies.io - @askashdavies

    View Slide

  110. View Slide