$30 off During Our Annual Pro Sale. View Details »

A Pragmatic Reactive Architecture for Android

A Pragmatic Reactive Architecture for Android

Presented on Mar '18 at Android Meetup at F22 Labs, Chennai.
https://www.meetup.com/Google-IO-Extended-Chennai/events/248296192/

Ragunath Jawahar

March 24, 2018
Tweet

More Decks by Ragunath Jawahar

Other Decks in Programming

Transcript

  1. A Pragmatic Reactive
    Architecture for Android
    Ragunath Jawahar
    ragunathjawahar / Medium • Twitter • GitHub

    View Slide

  2. View Slide

  3. Inspiration
    Cycle.js Redux

    View Slide

  4. But, why?

    View Slide

  5. RxJava

    View Slide

  6. Why?
    1. Predictable
    2. Platform Agnostic
    3. User-centric
    4. Reactive (Responsive / Resilient / Elastic / Message Driven)
    5. Unidirectional

    View Slide

  7. Model
    Presenter
    View

    View Slide

  8. Toolbox (Subjective)
    • Kotlin
    • RxJava
    • RxAndroid
    • RxKotlin (Optional)
    • RxBinding
    • Retrofit
    • Room / SQL Brite
    • JUnit
    • Truth
    • Mockito
    • Espresso

    View Slide

  9. Model ● View ● Intention

    View Slide

  10. View
    Model
    Intention

    View Slide

  11. EVERYTHING IS A
    STREAM
    Courtesy parkerblog.wordpress.com

    View Slide

  12. Intention

    View Slide

  13. Intent(ion) is a component whose sole
    responsibility is to translate user input
    events into model-friendly events.
    —André Staltz

    View Slide

  14. Intent(ion) is a component whose sole
    responsibility is to translate user input
    events into model-friendly events.
    —André Staltz

    View Slide

  15. Intent(ion) is a component whose sole
    responsibility is to translate user input
    events into model-friendly events.
    —André Staltz

    View Slide

  16. View
    Model
    Intention

    View Slide

  17. View Slide

  18. View Slide

  19. CounterIntentions.kt

    View Slide

  20. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  21. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  22. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  23. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  24. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  25. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  26. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  27. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  28. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  29. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  30. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  31. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  32. class CounterIntentions(
    private val incrementClicks: Observable,
    private val incrementBy5Clicks: Observable,
    private val decrementClicks: Observable,
    private val resetClicks: Observable
    ) {
    fun increment(): Observable =
    incrementClicks.map { +1 }
    // increment by 5 …
    fun decrement(): Observable =
    decrementClicks.map { -1 }
    fun reset(): Observable =
    resetClicks
    }

    View Slide

  33. Testing?

    View Slide

  34. Model

    View Slide

  35. View
    Model
    Intention

    View Slide

  36. Courtesy 123Countries.com
    State Model

    View Slide

  37. (Single Atom) State
    • Represents the state of your model
    • Should be normalized
    • UI state can always be derived from SAS, but not
    the other way around

    View Slide

  38. State Reducer
    Reducer
    State
    New
    State
    Signal

    View Slide

  39. CounterState.kt

    View Slide

  40. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  41. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  42. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  43. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  44. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  45. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  46. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  47. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  48. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  49. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  50. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  51. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  52. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  53. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  54. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  55. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  56. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  57. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  58. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  59. @Parcelize data class CounterState(
    val counter: Int
    ) : Parcelable {
    companion object {
    val ZERO = CounterState(0)
    }
    fun add(number: Int): CounterState =
    this.copy(counter = counter + number)
    fun reset(): CounterState =
    CounterState.ZERO
    }

    View Slide

  60. CounterModel.kt

    View Slide

  61. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  62. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  63. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  64. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  65. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  66. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  67. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  68. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  69. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  70. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  71. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  72. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  73. object CounterModel {
    fun bind(
    intentions: CounterIntentions,
    bindings: Observable,
    states: Observable
    ): Observable {
    val numbers = Observable.merge(
    intentions.increment(), intentions.incrementBy5(),
    intentions.decrement()
    )
    return Observable.merge(
    newBindingUseCase(bindings),
    restoredBindingUseCase(bindings, states),
    incrementDecrementUseCase(numbers, states),
    resetUseCase(intentions.reset())
    )
    }
    // More functions…
    }

    View Slide

  74. Use Case - New Binding
    private fun newBindingUseCase(
    bindings: Observable
    ): Observable {
    return bindings
    .filter { it == Binding.NEW }
    .map { CounterState.ZERO }
    }

    View Slide

  75. Use Case - New Binding
    private fun newBindingUseCase(
    bindings: Observable
    ): Observable {
    return bindings
    .filter { it == Binding.NEW }
    .map { CounterState.ZERO }
    }

    View Slide

  76. Use Case - New Binding
    private fun newBindingUseCase(
    bindings: Observable
    ): Observable {
    return bindings
    .filter { it == Binding.NEW }
    .map { CounterState.ZERO }
    }

    View Slide

  77. Use Case - New Binding
    private fun newBindingUseCase(
    bindings: Observable
    ): Observable {
    return bindings
    .filter { it == Binding.NEW }
    .map { CounterState.ZERO }
    }

    View Slide

  78. Use Case - New Binding
    private fun newBindingUseCase(
    bindings: Observable
    ): Observable {
    return bindings
    .filter { it == Binding.NEW }
    .map { CounterState.ZERO }
    }

    View Slide

  79. Use Case - New Binding
    private fun newBindingUseCase(
    bindings: Observable
    ): Observable {
    return bindings
    .filter { it == Binding.NEW }
    .map { CounterState.ZERO }
    }

    View Slide

  80. Use Case - Restored Binding
    private fun restoredBindingUseCase(
    bindings: Observable,
    states: Observable
    ): ObservableSource {
    return bindings
    .filter { it == Binding.RESTORED }
    .withLatestFrom(states) {
    _, previousState -> previousState
    }
    }

    View Slide

  81. Use Case - Restored Binding
    private fun restoredBindingUseCase(
    bindings: Observable,
    states: Observable
    ): ObservableSource {
    return bindings
    .filter { it == Binding.RESTORED }
    .withLatestFrom(states) {
    _, previousState -> previousState
    }
    }

    View Slide

  82. Use Case - Restored Binding
    private fun restoredBindingUseCase(
    bindings: Observable,
    states: Observable
    ): ObservableSource {
    return bindings
    .filter { it == Binding.RESTORED }
    .withLatestFrom(states) {
    _, previousState -> previousState
    }
    }

    View Slide

  83. Use Case - Restored Binding
    private fun restoredBindingUseCase(
    bindings: Observable,
    states: Observable
    ): ObservableSource {
    return bindings
    .filter { it == Binding.RESTORED }
    .withLatestFrom(states) {
    _, previousState -> previousState
    }
    }

    View Slide

  84. Use Case - Restored Binding
    private fun restoredBindingUseCase(
    bindings: Observable,
    states: Observable
    ): ObservableSource {
    return bindings
    .filter { it == Binding.RESTORED }
    .withLatestFrom(states) {
    _, previousState -> previousState
    }
    }

    View Slide

  85. Use Case - Restored Binding
    private fun restoredBindingUseCase(
    bindings: Observable,
    states: Observable
    ): ObservableSource {
    return bindings
    .filter { it == Binding.RESTORED }
    .withLatestFrom(states) {
    _, previousState -> previousState
    }
    }

    View Slide

  86. Use Case - Increment / Decrement
    private fun incrementDecrementUseCase(
    numbers: Observable,
    states: Observable
    ): Observable {
    return numbers.withLatestFrom(states) {
    number, previousState -> previousState.add(number)
    }
    }

    View Slide

  87. Use Case - Increment / Decrement
    private fun incrementDecrementUseCase(
    numbers: Observable,
    states: Observable
    ): Observable {
    return numbers.withLatestFrom(states) {
    number, previousState -> previousState.add(number)
    }
    }

    View Slide

  88. Use Case - Increment / Decrement
    private fun incrementDecrementUseCase(
    numbers: Observable,
    states: Observable
    ): Observable {
    return numbers.withLatestFrom(states) {
    number, previousState -> previousState.add(number)
    }
    }

    View Slide

  89. Use Case - Increment / Decrement
    private fun incrementDecrementUseCase(
    numbers: Observable,
    states: Observable
    ): Observable {
    return numbers.withLatestFrom(states) {
    number, previousState -> previousState.add(number)
    }
    }

    View Slide

  90. Use Case - Increment / Decrement
    private fun incrementDecrementUseCase(
    numbers: Observable,
    states: Observable
    ): Observable {
    return numbers.withLatestFrom(states) {
    number, previousState -> previousState.add(number)
    }
    }

    View Slide

  91. Use Case - Increment / Decrement
    private fun incrementDecrementUseCase(
    numbers: Observable,
    states: Observable
    ): Observable {
    return numbers.withLatestFrom(states) {
    number, previousState -> previousState.add(number)
    }
    }

    View Slide

  92. Use Case - Reset
    private fun resetUseCase(
    reset: Observable
    ): Observable {
    return reset.map { CounterState.ZERO }
    }

    View Slide

  93. Use Case - Reset
    private fun resetUseCase(
    reset: Observable
    ): Observable {
    return reset.map { CounterState.ZERO }
    }

    View Slide

  94. Use Case - Reset
    private fun resetUseCase(
    reset: Observable
    ): Observable {
    return reset.map { CounterState.ZERO }
    }

    View Slide

  95. Use Case - Reset
    private fun resetUseCase(
    reset: Observable
    ): Observable {
    return reset.map { CounterState.ZERO }
    }

    View Slide

  96. Use Case - Reset
    private fun resetUseCase(
    reset: Observable
    ): Observable {
    return reset.map { CounterState.ZERO }
    }

    View Slide

  97. Don’t scan() (Recommendation)
    • Introduces additional abstraction because it requires
    heterogeneous streams

    View Slide

  98. View

    View Slide

  99. View
    Model
    Intention

    View Slide

  100. CounterView.kt

    View Slide

  101. interface CounterView {
    fun displayCounter(value: Int)
    }

    View Slide

  102. interface CounterView {
    fun displayCounter(value: Int)
    }

    View Slide

  103. CounterViewDriver.kt

    View Slide

  104. class CounterViewDriver : ViewDriver {
    override fun render(view: CounterView, state: CounterState) {
    view.displayCounter(state.counter)
    }
    }

    View Slide

  105. class CounterViewDriver : ViewDriver {
    override fun render(view: CounterView, state: CounterState) {
    view.displayCounter(state.counter)
    }
    }

    View Slide

  106. class CounterViewDriver : ViewDriver {
    override fun render(view: CounterView, state: CounterState) {
    view.displayCounter(state.counter)
    }
    }

    View Slide

  107. class CounterViewDriver : ViewDriver {
    override fun render(view: CounterView, state: CounterState) {
    view.displayCounter(state.counter)
    }
    }

    View Slide

  108. class CounterViewDriver : ViewDriver {
    override fun render(view: CounterView, state: CounterState) {
    view.displayCounter(state.counter)
    }
    }

    View Slide

  109. class CounterViewDriver : ViewDriver {
    override fun render(view: CounterView, state: CounterState) {
    view.displayCounter(state.counter)
    }
    }

    View Slide

  110. The Good
    • Wholistic
    • Testable
    • Easy to debug
    • Reduction in the number of unknowns

    View Slide

  111. The Bad
    • Very steep learning curve

    View Slide

  112. View Slide

  113. References
    • https://www.youtube.com/watch?v=1zj7M1LnJV4
    • https://cycle.js.org/getting-started.html
    • https://egghead.io/courses/cycle-js-fundamentals
    • https://redux.js.org
    • https://egghead.io/courses/getting-started-with-redux
    • https://github.com/ragunathjawahar/kitchen-sink

    View Slide

  114. Medium • Twitter • GitHub
    @ragunathjawahar

    View Slide