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
Sponsored
·
Ship Features Fearlessly
Turn features on and off without deploys. Used by thousands of Ruby developers.
→
Ragunath Jawahar
April 21, 2018
Programming
460
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
65
The Hidden Cognitive Costs of AI-assisted Programming @ GIDS 2026
ragunathjawahar
1
110
AI-Native Codebases
ragunathjawahar
1
95
AI-Native Codebases x BLR Kotlin
ragunathjawahar
1
50
Single-responsibility principle meets the real world! (Dubai Edition)
ragunathjawahar
0
91
Single-responsibility principle meets the real world!
ragunathjawahar
0
200
Making sense of large Java and Kotlin classes
ragunathjawahar
1
330
Building Robust Software, Episode 3
ragunathjawahar
1
210
Building Robust Software, Episode 2
ragunathjawahar
1
170
Other Decks in Programming
See All in Programming
気圧・高度・GPSを記録&可視化するアプリ「Koudo」を作った話
hjmkth
1
360
act2-costs.pdf
sumedhbala
0
120
Claude Team Plan導入・ガイド
tk3fftk
0
210
【やさしく解説 設計編・中級 #4】ルールの寿命と、システムの年輪
panda728
PRO
2
150
ITヒヤリハットを整理してみた ~ライフサイクルと原因から考える再発防止策~
koukimiura
1
110
ソフトウェア設計に溶けるインフラ ― AWS CDK のインフラ認識論
konokenj
2
570
AI時代、エンジニアはどう育つのか -未経験エンジニアの成長を間近で見て考えたこと-
thasu0123
0
130
コーディングルールの鮮度を保ちたい for SRE NEXT 2026 / keep-fresh-go-internal-conventions-sre-next-2026
handlename
0
140
えっ!!コードを読まずに開発を!?
hananouchi
0
220
なぜ関数型プログラミングで「型」と「証明」が語られるのか #fp_matsuri
kajitack
3
970
symfony/aiとlaravel/boost
77web
0
140
「正の参照」と 「負の導出」で組む ハーネスエンジニアリング
cottpan
1
140
Featured
See All Featured
Sam Torres - BigQuery for SEOs
techseoconnect
PRO
0
310
How to build a perfect <img>
jonoalderson
1
5.8k
Imperfection Machines: The Place of Print at Facebook
scottboms
270
14k
The AI Revolution Will Not Be Monopolized: How open-source beats economies of scale, even for LLMs
inesmontani
PRO
3
3.6k
The State of eCommerce SEO: How to Win in Today's Products SERPs - #SEOweek
aleyda
2
11k
What’s in a name? Adding method to the madness
productmarketing
PRO
24
4.1k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1k
世界の人気アプリ100個を分析して見えたペイウォール設計の心得
akihiro_kokubo
PRO
72
40k
Design and Strategy: How to Deal with People Who Don’t "Get" Design
morganepeng
133
19k
How to make the Groovebox
asonas
2
2.3k
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.7k
B2B Lead Gen: Tactics, Traps & Triumph
marketingsoph
0
180
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