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

Rx in Multiplatformland (MobileOptimized 2019)

Rx in Multiplatformland (MobileOptimized 2019)

File with original colors can be downloaded here: https://bit.ly/2N1wlF6

Reactive programming has firmly become one of the modern Android developer's tools. More and more teams apply fully reactive architectures for their apps utilizing MV* approaches. This plays well for separating our OS-independent business logic from presentation layer. And while it can be implemented really nice using Kotlin, what if we want to expand this solid base further and don't repeat ourselves on every platform? What if we want to enter Multiplatformland?

Obviously, our best friend, RxJava, is not ready for that journey. But do we really have no alternatives? In this talk we'll discuss multiplatform friends of RxJava, such as Flow from Kotlin team and Reaktive from Badoo, their pros & cons, and how usable are they for our tasks.

Video: https://youtu.be/FazAHlmlwws

C50a1f407bc251b7395c0984be4327e9?s=128

Sergey Ryabov

October 19, 2019
Tweet

More Decks by Sergey Ryabov

Other Decks in Programming

Transcript

  1. Rx in Multiplatformland State of Reactive Streams in Kotlin Sergey

    Ryabov @colriot
  2. Last year at Mobius ✦ "How to cook a well

    done MVI for Android" talk1 ✦ Platform-agnostic ✦ Based on Kotlin. MPP! ! ✦ Based on RxJava. MPP? " ✦ Maybe RxKotlin? (for real) 1 https:/ /www.youtube.com/watch?v=hBkQkjWnAjg
  3. This year ✦ In March my friend Kostya sent me

    a link to a Reaktive repo ✦ In April Roman wrote an article about Flow Preview2 2 https:/ /medium.com/@elizarov/cold-flows-hot-channels-d74769805f9
  4. Fast-forward 6 months ✦ Reaktive - 1.0.1 ✦ Flow -

    stable since Coroutines 1.3.0 ✦ Supported by SQLDelight 1.2.0 ✦ Supported by Room 2.2.0
  5. RxJava2 Observable.just(1, 2, 3) .filter { it % 2 ==

    0 } .map { it * 10 } .subscribe { println(it) }
  6. Reaktive observableOf(1, 2, 3) .filter { it % 2 ==

    0 } .map { it * 10 } .subscribe { println(it) }
  7. Flow flowOf(1, 2, 3) .filter { it % 2 ==

    0 } .map { it * 10 } .collect { println(it) }
  8. Flow flowOf(1, 2, 3) .filter { it % 2 ==

    0 } .map { it * 10 } .collect { println(it) }
  9. Flow. In a nutshell interface Flow<out T> { suspend fun

    collect(collector: FlowCollector<T>) } interface FlowCollector<in T> { suspend fun emit(value: T) }
  10. How does it work? Flow fun <T, R> Flow<T>.map(transform: suspend

    (value: T) -> R): Flow<R> = flow { collect { value -> emit(transform(value)) } }
  11. How does it work? Flow fun <T, R> Flow<T>.map(transform: suspend

    (value: T) -> R): Flow<R> = flow { this: FlowCollector -> this@map.collect { value -> this.emit(transform(value)) } }
  12. How does it work? Flow fun <T, R> Flow<T>.map(transform: suspend

    (value: T) -> R): Flow<R> = flow { this: FlowCollector -> this@map.collect { value -> this.emit(transform(value)) } }
  13. How does it work? Flow fun <T, R> Flow<T>.map(transform: suspend

    (value: T) -> R): Flow<R> = flow { this: FlowCollector -> this@map.collect { value -> this.emit(transform(value)) } }
  14. How does it work? Flow fun <T, R> Flow<T>.map(transform: suspend

    (value: T) -> R): Flow<R> = flow { this: FlowCollector -> this@map.collect { value -> this.emit(transform(value)) } }
  15. How does it work? Flow fun <T, R> Flow<T>.map(transform: suspend

    (value: T) -> R): Flow<R> = flow { this: FlowCollector -> this@map.collect { value -> this.emit(transform(value)) } }
  16. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  17. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  18. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  19. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  20. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  21. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  22. How does it work? Reaktive fun <T, R> Observable<T>.map(mapper: (T)

    -> R): Observable<R> = observable { emitter -> subscribeSafe( object : ObservableObserver<T>, CompletableCallbacks by emitter { override fun onSubscribe(disposable: Disposable) { emitter.setDisposable(disposable) } override fun onNext(value: T) { emitter.tryCatch(block = { mapper(value) }, onSuccess = emitter::onNext) } } ) }
  23. How does it work? RxJa... oh, damn public final class

    ObservableMap<T, U> extends AbstractObservableWithUpstream<T, U> { final Function<? super T, ? extends U> function; public ObservableMap(ObservableSource<T> source, Function<? super T, ? extends U> function) { super(source); this.function = function; } @Override public void subscribeActual(Observer<? super U> t) { source.subscribe(new MapObserver<T, U>(t, function)); } static final class MapObserver<T, U> extends BasicFuseableObserver<T, U> { final Function<? super T, ? extends U> mapper; MapObserver(Observer<? super U> actual, Function<? super T, ? extends U> mapper) { super(actual); this.mapper = mapper; } @Override public void onNext(T t) { if (done) { return; } if (sourceMode != NONE) { downstream.onNext(null); return; } U v; try { v = ObjectHelper.requireNonNull(mapper.apply(t), "The mapper function returned a null value."); } catch (Throwable ex) { fail(ex); return; } downstream.onNext(v); } @Override public int requestFusion(int mode) { return transitiveBoundaryFusion(mode); } @Nullable @Override public U poll() throws Exception { T t = qd.poll(); return t != null ? ObjectHelper.<U>requireNonNull(mapper.apply(t), "The mapper function returned a null value.") : null; } } }
  24. Operators Reaktive Flow flatMap flatMapMerge concatMap flatMapConcat switchMap flatMapLatest flatten

    flattenConcat merge flattenMerge combineLatest combine skip drop onError* catch ... ...
  25. Missing in Flow ✦ groupBy ✦ publish ✦ share 3

    ✦ replay ✦ withLatestFrom ✦ takeUntil ✦ parallel 3 https:/ /github.com/Kotlin/kotlinx.coroutines/issues/1261
  26. Missing in Flow ✦ groupBy 5 ✦ publish 5 ✦

    share ✦ replay 5 ✦ withLatestFrom ✦ takeUntil 5 ✦ parallel 5 5 https:/ /github.com/akarnokd/kotlin-flow-extensions
  27. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  28. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  29. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  30. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  31. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  32. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  33. Threading. Reaktive observableOf(1, 2, 3) .filter { it % 2

    == 0 } .subscribeOn(ioScheduler) .observeOn(computationScheduler) .map { it * 10 } .observeOn(mainScheduler) .subscribe { println(it) }
  34. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  35. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  36. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  37. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  38. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  39. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  40. Threading. Flow scope.launch(Dispatchers.MAIN) { flowOf(1, 2, 3) .filter { it

    % 2 == 0 } .flowOn(Dispatchers.IO) .map { it * 10 } .flowOn(Dispatchers.Default) .collect { println(it) } }
  41. Schedulers RxJava2 Reaktive Flow AndroidSchedulers.mainThread() mainScheduler Dispatchers.MAIN Schedulers.io() ioScheduler Dispatchers.IO

    6 Schedulers.computation() computationScheduler Dispatchers.Default Schedulers.trampoline() trampolineScheduler Executor.asCoroutineDispatcher() 6 Schedulers.newThread() i.r.Scheduler.asReaktive() 6 Executor.asCoroutineDispatcher() 6 Schedulers.single() i.r.Scheduler.asReaktive() 6 Executor.asCoroutineDispatcher() 6 6 only on JVM
  42. Subjects ✦ RxJava2: Behavior, Publish, Replay ✦ Reaktive: Behavior, Publish

    ✦ Flow: Behavior (ConflatedBroadcastChannel 7) ✦ Flow + Karnok5: Behavior, Publish, Replay 5 https:/ /github.com/akarnokd/kotlin-flow-extensions 7 DataFlow Proposal: https:/ /github.com/Kotlin/kotlinx.coroutines/pull/1354
  43. Interop & Gradual adoption Adapter artifacts: ✦ org.jetbrains.kotlinx:kotlinx-coroutines-rx2 ✦ com.badoo.reaktive:coroutines-interop

    ✦ com.badoo.reaktive:rxjava2-interop
  44. Interop & Gradual adoption. Flow Check this out: kotlinx-coroutines-core/common/src/flow/Migration.kt

  45. Interop & Gradual adoption. Flow Check this out: kotlinx-coroutines-core/common/src/flow/Migration.kt Deprecated(

    level = DeprecationLevel.ERROR, message = "Flow analogue of 'flatten' is 'flattenConcat'", replaceWith = ReplaceWith("flattenConcat()") ) fun <T> Flow<Flow<T>>.flatten(): Flow<T> = noImpl()
  46. Error handling bits dataObservable() .flatMap { crashMap(it) } .subscribe( onNext

    = { handle(it) }, onError = { report(it) } )
  47. Error handling bits dataObservable() .flatMap { map(it) } .subscribe( onNext

    = { crashHandle(it) }, onError = { report(it) } )
  48. Error handling bits. Flow dataFlow() .flatMap { map(it) } .subscribe(

    onNext = { crashHandle(it) }, onError = { report(it) } )
  49. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .subscribe(

    onNext = { crashHandle(it) }, onError = { report(it) } )
  50. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .catch

    { report(it) } .subscribe( onNext = { crashHandle(it) } )
  51. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .catch

    { report(it) } .collect { crashHandle(it) }
  52. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .catch

    { report(it) } .collect { crashHandle(it) } We crash!
  53. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .onEach

    { crashHandle(it) } .catch { report(it) } .collect()
  54. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .onEach

    { crashHandle(it) } .catch { report(it) } .collect()
  55. Error handling bits. Flow dataFlow() .flatMapMerge { map(it) } .onEach

    { crashHandle(it) } .catch { report(it) } // <-- last in chain .collect() You're not in Kansas anymore. Pay attention! 10 11 11 https:/ /proandroiddev.com/kotlin-coroutine-job-hierarchy-finish-cancel-and-fail-2d3d42a768a9 10 https:/ /github.com/MartinDevi/Coroutines-Completion-Demo
  56. Kotlin Native

  57. Kotlin Native. Flow Single-threaded coroutines.8 8 https:/ /github.com/Kotlin/kotlinx.coroutines/issues/462

  58. Kotlin Native. Flow Single-threaded coroutines.8 Single-threaded flows. 8 https:/ /github.com/Kotlin/kotlinx.coroutines/issues/462

  59. Kotlin Native. Flow Single-threaded coroutines.8 Single-threaded flows. Bye. 8 https:/

    /github.com/Kotlin/kotlinx.coroutines/issues/462
  60. Kotlin Native. Reaktive ✦ Workers-based threading ✦ Native freeze for

    switching threads, i.e. almost always ✦ threadLocal operator to stop freezing ✦ In practice, not only immutable events, but also captured vars in callbacks
  61. Performance 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 156.474 ms/op

    RxJava2ObservablePlaysScrabble avgt 90.505 ms/op RxJava2FlowablePlaysScrabble avgt 96.144 ms/op FlowPlaysScrabble avgt 84.520 ms/op ReaktivePlaysScrabble avgt 208.666 ms/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  62. Performance 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 156.474 ms/op

    RxJava2ObservablePlaysScrabble avgt 90.505 ms/op RxJava2FlowablePlaysScrabble avgt 96.144 ms/op FlowPlaysScrabble avgt 84.520 ms/op ReaktivePlaysScrabble avgt 208.666 ms/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  63. Performance 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 156.474 ms/op

    RxJava2ObservablePlaysScrabble avgt 90.505 ms/op RxJava2FlowablePlaysScrabble avgt 96.144 ms/op FlowPlaysScrabble avgt 84.520 ms/op ReaktivePlaysScrabble avgt 208.666 ms/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  64. Performance 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 156.474 ms/op

    RxJava2ObservablePlaysScrabble avgt 90.505 ms/op RxJava2FlowablePlaysScrabble avgt 96.144 ms/op FlowPlaysScrabble avgt 84.520 ms/op ReaktivePlaysScrabble avgt 208.666 ms/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  65. Performance 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 156.474 ms/op

    RxJava2ObservablePlaysScrabble avgt 90.505 ms/op RxJava2FlowablePlaysScrabble avgt 96.144 ms/op FlowPlaysScrabble avgt 84.520 ms/op FlowPlaysScrabbleFlatMap avgt 744.464 ms/op ReaktivePlaysScrabble avgt 208.666 ms/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  66. Memory 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 254.334 MB/op

    RxJava2ObservablePlaysScrabble avgt 123.316 MB/op RxJava2FlowablePlaysScrabble avgt 139.861 MB/op FlowPlaysScrabble avgt 247.987 MB/op FlowPlaysScrabbleFlatMap avgt 780.728 MB/op ReaktivePlaysScrabble avgt 351.192 MB/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  67. Memory 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 254.334 MB/op

    RxJava2ObservablePlaysScrabble avgt 123.316 MB/op RxJava2FlowablePlaysScrabble avgt 139.861 MB/op FlowPlaysScrabble avgt 247.987 MB/op FlowPlaysScrabbleFlatMap avgt 780.728 MB/op ReaktivePlaysScrabble avgt 351.192 MB/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  68. Memory 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 254.334 MB/op

    RxJava2ObservablePlaysScrabble avgt 123.316 MB/op RxJava2FlowablePlaysScrabble avgt 139.861 MB/op FlowPlaysScrabble avgt 247.987 MB/op FlowPlaysScrabbleFlatMap avgt 780.728 MB/op ReaktivePlaysScrabble avgt 351.192 MB/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  69. Memory 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 254.334 MB/op

    RxJava2ObservablePlaysScrabble avgt 123.316 MB/op RxJava2FlowablePlaysScrabble avgt 139.861 MB/op FlowPlaysScrabble avgt 247.987 MB/op FlowPlaysScrabbleFlatMap avgt 780.728 MB/op ReaktivePlaysScrabble avgt 351.192 MB/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  70. Memory 9 Benchmark Mode Score Units RxJava1ObservablePlaysScrabble avgt 254.334 MB/op

    RxJava2ObservablePlaysScrabble avgt 123.316 MB/op RxJava2FlowablePlaysScrabble avgt 139.861 MB/op FlowPlaysScrabble avgt 247.987 MB/op FlowPlaysScrabbleFlatMap avgt 780.728 MB/op ReaktivePlaysScrabble avgt 351.192 MB/op 9 https:/ /github.com/akarnokd/akarnokd-misc
  71. Consclusion ✦ Flow ✦ Not yet ready for MPP. Native?

    ✦ Not all operators we are used to are available ✦ Official support from JetBrains & Google ✦ Reaktive ✦ Real-ish Native support ✦ 1.0.x, but not really production ready ✦ Native affects performance on other platforms (JVM) - Is being FIXED ✦ Very responsive maintainers "
  72. Links ✦ Reaktive repo github.com/badoo/Reaktive ✦ Flow docs kotlinlang.org/docs/reference/coroutines/flow.html ✦

    Reactive Scrabble Benchmark akarnokd.blogspot.com/2016/12/the- reactive-scrabble-benchmarks.html ✦ Job Hierarchy ambiguity proandroiddev.com/kotlin-coroutine-job- hierarchy-finish-cancel-and-fail-2d3d42a768a9 ✦ Sample code github.com/colriot/talk-rx-multiplatform ✦ Cover photo unsplash.com/photos/PqU63ujaIa4
  73. Rx in Multiplatformland State of Reactive Streams in Kotlin Q/A

    Sergey Ryabov @colriot