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
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
62
The Hidden Cognitive Costs of AI-assisted Programming @ GIDS 2026
ragunathjawahar
1
96
AI-Native Codebases
ragunathjawahar
1
84
AI-Native Codebases x BLR Kotlin
ragunathjawahar
1
45
Single-responsibility principle meets the real world! (Dubai Edition)
ragunathjawahar
0
86
Single-responsibility principle meets the real world!
ragunathjawahar
0
190
Making sense of large Java and Kotlin classes
ragunathjawahar
1
320
Building Robust Software, Episode 3
ragunathjawahar
1
210
Building Robust Software, Episode 2
ragunathjawahar
1
170
Other Decks in Programming
See All in Programming
TAKTでAI駆動開発の品質を設計する
j5ik2o
6
1.2k
Semantic Version 単位で戦略を柔軟に変えて、パッケージアップデートを自動化する
daitasu
0
220
Claspは野良GASの夢をみるか
takter00
0
180
技術記事、AIに書かせるか、自分で書くか? 〜それでも私が自分の手で書く理由〜 / #QiitaConference
jnchito
2
1.4k
ふつうのFeature Flag実践入門
irof
7
3.7k
ECSアプリログをFireLensでコスト削減しようとしたけど諦めた話 in Fargate×Node.js
akihisaikeda
2
4k
AI時代の仕事技芸論 — ソフトウェア開発で「遊ぶように働く」職人的熟達のすすめ
kuranuki
1
660
OSもどきOS
arkw
0
510
「なぜそう決めたのか」を残し続ける仕組み ― Notion AI カスタムエージェント × Slack連携による設計判断の自動記録 - NIKKEI Tech Talk #47
niftycorp
PRO
0
110
Lemonade + Foundry Toolkit でお手軽アプリ開発
seosoft
1
320
セキュリティの専門家じゃなくてもできる。「セキュリティ意識」をアップデートして サプライチェーン攻撃への耐性を高めよう。
tk3fftk
5
710
CSC307 Lecture 17
javiergs
PRO
0
320
Featured
See All Featured
The SEO Collaboration Effect
kristinabergwall1
1
480
Stewardship and Sustainability of Urban and Community Forests
pwiseman
0
220
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
231
23k
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
10k
Why Our Code Smells
bkeepers
PRO
340
58k
Facilitating Awesome Meetings
lara
57
7k
Bash Introduction
62gerente
615
220k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.5k
Google's AI Overviews - The New Search
badams
0
1k
Balancing Empowerment & Direction
lara
6
1.2k
GitHub's CSS Performance
jonrohan
1033
470k
HTML-Aware ERB: The Path to Reactive Rendering @ RubyCon 2026, Rimini, Italy
marcoroth
1
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