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
4
410
How to MVI?
Presented on April '18 at BlrDroid 101.
https://www.meetup.com/blrdroid/events/249149873/
Ragunath Jawahar
April 21, 2018
Tweet
Share
More Decks by Ragunath Jawahar
See All by Ragunath Jawahar
Single-responsibility principle meets the real world! (Dubai Edition)
ragunathjawahar
0
20
Single-responsibility principle meets the real world!
ragunathjawahar
0
130
Making sense of large Java and Kotlin classes
ragunathjawahar
1
210
Building Robust Software, Episode 3
ragunathjawahar
1
120
Building Robust Software, Episode 2
ragunathjawahar
1
75
Building Robust Software (Episode 1)
ragunathjawahar
1
290
Speed as a workplace habit
ragunathjawahar
1
260
Building Robust Apps (Swift Edition)
ragunathjawahar
0
190
Re-architecture
ragunathjawahar
0
410
Other Decks in Programming
See All in Programming
ECSのサービス間通信 4つの方法を比較する 〜Canary,Blue/Greenも添えて〜
tkikuc
11
2.3k
Amazon Neptuneで始めてみるグラフDB-OpenSearchによるグラフの全文検索-
satoshi256kbyte
4
330
プロジェクト新規参入者のリードタイム短縮の観点から見る、品質の高いコードとアーキテクチャを保つメリット
d_endo
1
1k
Modern Angular: Renovation for Your Applications
manfredsteyer
PRO
0
210
カラム追加で増えるActiveRecordのメモリサイズ イメージできますか?
asayamakk
4
1.6k
Piniaの現状と今後
waka292
5
1.5k
のびしろを広げる巻き込まれ力:偶然を活かすキャリアの作り方/oso2024
takahashiikki
1
410
Content Security Policy入門 セキュリティ設定と 違反レポートのはじめ方 / Introduction to Content Security Policy Getting Started with Security Configuration and Violation Reporting
uskey512
1
430
Webの技術スタックで マルチプラットフォームアプリ開発を可能にするElixirDesktopの紹介
thehaigo
2
920
レガシーな Android アプリのリアーキテクチャ戦略
oidy
1
170
WEBエンジニア向けAI活用入門
sutetotanuki
0
300
Streams APIとTCPフロー制御 / Web Streams API and TCP flow control
tasshi
1
290
Featured
See All Featured
A Philosophy of Restraint
colly
203
16k
Product Roadmaps are Hard
iamctodd
PRO
48
10k
Music & Morning Musume
bryan
46
6.1k
Designing for humans not robots
tammielis
249
25k
Gamification - CAS2011
davidbonilla
80
5k
Building a Scalable Design System with Sketch
lauravandoore
459
33k
Bootstrapping a Software Product
garrettdimon
PRO
305
110k
Code Reviewing Like a Champion
maltzj
519
39k
Build The Right Thing And Hit Your Dates
maggiecrowley
32
2.4k
個人開発の失敗を避けるイケてる考え方 / tips for indie hackers
panda_program
92
16k
GraphQLとの向き合い方2022年版
quramy
43
13k
How to Ace a Technical Interview
jacobian
275
23k
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