Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
How to MVI?
Search
Ragunath Jawahar
April 21, 2018
Programming
470
4
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
How to MVI?
Presented on April '18 at BlrDroid 101.
https://www.meetup.com/blrdroid/events/249149873/
Ragunath Jawahar
April 21, 2018
More Decks by Ragunath Jawahar
See All by Ragunath Jawahar
Context Management for Everyday Software Engineering
ragunathjawahar
1
76
The Hidden Cognitive Costs of AI-assisted Programming @ GIDS 2026
ragunathjawahar
1
130
AI-Native Codebases
ragunathjawahar
1
110
AI-Native Codebases x BLR Kotlin
ragunathjawahar
1
56
Single-responsibility principle meets the real world! (Dubai Edition)
ragunathjawahar
0
100
Single-responsibility principle meets the real world!
ragunathjawahar
0
210
Making sense of large Java and Kotlin classes
ragunathjawahar
1
340
Building Robust Software, Episode 3
ragunathjawahar
1
220
Building Robust Software, Episode 2
ragunathjawahar
1
180
Other Decks in Programming
See All in Programming
{ Android | Kotlin } Gradle Plugin in 2026
ryunen344
1
280
tsc.rip を支える技術 / Kyoto.なんか #8
susisu
0
4.3k
バグを直したら useEffect が消えた
colorful12
3
840
FDEとは、何者なのか?
masapyon1212
0
140
AI時代に学ぶ 好きなルール 嫌いなルール Linter編
shorty5121
0
830
【デモ】Kiroで体験する仕様駆動開発|設計からコーディングまでAIと進める開発フロー
cmkudo
0
570
LLMは4年分のCompose移行を再現できるのか?実プロダクト279件のXMLで探る自動化の境界線
makun
0
380
GKE アップグレード前に知っておきたい Blue/Green と PDB の関係
stkk
0
140
信頼性の目標を誰も求めてない
shubox
0
490
AWS DevOps Agentで インシデント対応をAIに任せたい
honmarkhunt
4
890
go-spidermonkeyでAIエージェントのCode Modeを実装する
syumai
3
1.5k
ハーネス設計入門 〜プロンプト、コンテキストの次〜
kinopeee
53
35k
Featured
See All Featured
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
270
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
WENDY [Excerpt]
tessaabrams
12
39k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
770
Designing Experiences People Love
moore
143
24k
Building the Perfect Custom Keyboard
takai
2
860
Google's AI Overviews - The New Search
badams
0
1.6k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
31
3.3k
Build your cross-platform service in a week with App Engine
jlugia
234
19k
Side Projects
sachag
455
43k
Taking LLMs out of the black box: A practical guide to human-in-the-loop distillation
inesmontani
PRO
3
2.4k
The Curious Case for Waylosing
cassininazir
1
490
Transcript
How to MVI? Ragunath Jawahar ragunathjawahar / Medium • Twitter
• GitHub
*This statement is architecture / platform agnostic
Why? Courtesy pexels.com/@goumbik
Why?
Background 1. Several iterations since April ’17 2. Maximize testability
and minimize code 3. Offline-first app with device capabilities 4. Both Android and iOS
Testing Goals UI Tests Instrumented Tests Unit Tests **Not to
scale
Testing Goals UI Tests Instrumented Tests Unit Tests **Not to
scale
Testing Goals UI Tests Instrumented Tests Unit Tests **Not to
scale
Testing Goals UI Tests Instrumented Tests Unit Tests **Not to
scale
Benefits 1. Predictable 2. Platform Agnostic 3. User-centric 4. Reactive
(Responsive / Resilient / Elastic / Message Driven) 5. Unidirectional
Model Presenter View
Toolbox (Subjective) • Kotlin • RxJava • RxAndroid • RxKotlin
(Optional) • RxBinding • Retrofit • Room / SQL Brite • JUnit • Truth • Mockito • Espresso
Model • View • Intention
View Model Intention
EVERYTHING IS A STREAM Courtesy parkerblog.wordpress.com
Intention
Intent(ion) is a component whose sole responsibility is to translate
user input events into model-friendly events. —André Staltz
Intent(ion) is a component whose sole responsibility is to translate
user input events into model-friendly events. —André Staltz
Intent(ion) is a component whose sole responsibility is to translate
user input events into model-friendly events. —André Staltz
View Model Intention
None
None
CounterIntentions.kt
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
class CounterIntentions( private val incrementClicks: Observable<Unit>, private val incrementBy5Clicks: Observable<Unit>,
private val decrementClicks: Observable<Unit>, private val resetClicks: Observable<Unit> ) { fun increment(): Observable<Int> = incrementClicks.map { +1 } // increment by 5 … fun decrement(): Observable<Int> = decrementClicks.map { -1 } fun reset(): Observable<Unit> = resetClicks }
Testing?
Model
View Model Intention
Courtesy 123Countries.com State Model
(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
State Reducer Reducer State New State Signal
CounterState.kt
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
@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 }
CounterModel.kt
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
object CounterModel { fun bind( intentions: CounterIntentions, bindings: Observable<Binding>, states:
Observable<CounterState> ): Observable<CounterState> { 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… }
Use Case - New Binding private fun newBindingUseCase( bindings: Observable<Binding>
): Observable<CounterState> { return bindings .filter { it == Binding.NEW } .map { CounterState.ZERO } }
Use Case - New Binding private fun newBindingUseCase( bindings: Observable<Binding>
): Observable<CounterState> { return bindings .filter { it == Binding.NEW } .map { CounterState.ZERO } }
Use Case - New Binding private fun newBindingUseCase( bindings: Observable<Binding>
): Observable<CounterState> { return bindings .filter { it == Binding.NEW } .map { CounterState.ZERO } }
Use Case - New Binding private fun newBindingUseCase( bindings: Observable<Binding>
): Observable<CounterState> { return bindings .filter { it == Binding.NEW } .map { CounterState.ZERO } }
Use Case - New Binding private fun newBindingUseCase( bindings: Observable<Binding>
): Observable<CounterState> { return bindings .filter { it == Binding.NEW } .map { CounterState.ZERO } }
Use Case - New Binding private fun newBindingUseCase( bindings: Observable<Binding>
): Observable<CounterState> { return bindings .filter { it == Binding.NEW } .map { CounterState.ZERO } }
Use Case - Restored Binding private fun restoredBindingUseCase( bindings: Observable<Binding>,
states: Observable<CounterState> ): ObservableSource<CounterState> { return bindings .filter { it == Binding.RESTORED } .withLatestFrom(states) { _, previousState -> previousState } }
Use Case - Restored Binding private fun restoredBindingUseCase( bindings: Observable<Binding>,
states: Observable<CounterState> ): ObservableSource<CounterState> { return bindings .filter { it == Binding.RESTORED } .withLatestFrom(states) { _, previousState -> previousState } }
Use Case - Restored Binding private fun restoredBindingUseCase( bindings: Observable<Binding>,
states: Observable<CounterState> ): ObservableSource<CounterState> { return bindings .filter { it == Binding.RESTORED } .withLatestFrom(states) { _, previousState -> previousState } }
Use Case - Restored Binding private fun restoredBindingUseCase( bindings: Observable<Binding>,
states: Observable<CounterState> ): ObservableSource<CounterState> { return bindings .filter { it == Binding.RESTORED } .withLatestFrom(states) { _, previousState -> previousState } }
Use Case - Restored Binding private fun restoredBindingUseCase( bindings: Observable<Binding>,
states: Observable<CounterState> ): ObservableSource<CounterState> { return bindings .filter { it == Binding.RESTORED } .withLatestFrom(states) { _, previousState -> previousState } }
Use Case - Restored Binding private fun restoredBindingUseCase( bindings: Observable<Binding>,
states: Observable<CounterState> ): ObservableSource<CounterState> { return bindings .filter { it == Binding.RESTORED } .withLatestFrom(states) { _, previousState -> previousState } }
Use Case - Increment / Decrement private fun incrementDecrementUseCase( numbers:
Observable<Int>, states: Observable<CounterState> ): Observable<CounterState> { return numbers.withLatestFrom(states) { number, previousState -> previousState.add(number) } }
Use Case - Increment / Decrement private fun incrementDecrementUseCase( numbers:
Observable<Int>, states: Observable<CounterState> ): Observable<CounterState> { return numbers.withLatestFrom(states) { number, previousState -> previousState.add(number) } }
Use Case - Increment / Decrement private fun incrementDecrementUseCase( numbers:
Observable<Int>, states: Observable<CounterState> ): Observable<CounterState> { return numbers.withLatestFrom(states) { number, previousState -> previousState.add(number) } }
Use Case - Increment / Decrement private fun incrementDecrementUseCase( numbers:
Observable<Int>, states: Observable<CounterState> ): Observable<CounterState> { return numbers.withLatestFrom(states) { number, previousState -> previousState.add(number) } }
Use Case - Increment / Decrement private fun incrementDecrementUseCase( numbers:
Observable<Int>, states: Observable<CounterState> ): Observable<CounterState> { return numbers.withLatestFrom(states) { number, previousState -> previousState.add(number) } }
Use Case - Increment / Decrement private fun incrementDecrementUseCase( numbers:
Observable<Int>, states: Observable<CounterState> ): Observable<CounterState> { return numbers.withLatestFrom(states) { number, previousState -> previousState.add(number) } }
Use Case - Reset private fun resetUseCase( reset: Observable<Unit> ):
Observable<CounterState> { return reset.map { CounterState.ZERO } }
Use Case - Reset private fun resetUseCase( reset: Observable<Unit> ):
Observable<CounterState> { return reset.map { CounterState.ZERO } }
Use Case - Reset private fun resetUseCase( reset: Observable<Unit> ):
Observable<CounterState> { return reset.map { CounterState.ZERO } }
Use Case - Reset private fun resetUseCase( reset: Observable<Unit> ):
Observable<CounterState> { return reset.map { CounterState.ZERO } }
Use Case - Reset private fun resetUseCase( reset: Observable<Unit> ):
Observable<CounterState> { return reset.map { CounterState.ZERO } }
Don’t scan() (Recommendation) • Introduces additional abstraction because it requires
homogeneous streams
View
View Model Intention
CounterView.kt
interface CounterView { fun displayCounter(value: Int) }
interface CounterView { fun displayCounter(value: Int) }
CounterViewDriver.kt
class CounterViewDriver : ViewDriver<CounterView, CounterState> { override fun render(view: CounterView,
state: CounterState) { view.displayCounter(state.counter) } }
class CounterViewDriver : ViewDriver<CounterView, CounterState> { override fun render(view: CounterView,
state: CounterState) { view.displayCounter(state.counter) } }
class CounterViewDriver : ViewDriver<CounterView, CounterState> { override fun render(view: CounterView,
state: CounterState) { view.displayCounter(state.counter) } }
class CounterViewDriver : ViewDriver<CounterView, CounterState> { override fun render(view: CounterView,
state: CounterState) { view.displayCounter(state.counter) } }
class CounterViewDriver : ViewDriver<CounterView, CounterState> { override fun render(view: CounterView,
state: CounterState) { view.displayCounter(state.counter) } }
class CounterViewDriver : ViewDriver<CounterView, CounterState> { override fun render(view: CounterView,
state: CounterState) { view.displayCounter(state.counter) } }
Learnings • RxJava yields better results with a reactive architecture
• You don’t need lifecycle events (most of the time) • UI tests are necessary • Collaboration between mobile teams is possible and fun
Pros • Wholistic • Testable • Easy to debug •
Reduction in the number of unknowns
Cons
Cons In Words • Learning curve • Buy-in from team
/ tech leaders • Hiring (as of today)
None
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
Medium • Twitter • GitHub @ragunathjawahar