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
16
Single-responsibility principle meets the real world!
ragunathjawahar
0
130
Making sense of large Java and Kotlin classes
ragunathjawahar
1
180
Building Robust Software, Episode 3
ragunathjawahar
1
120
Building Robust Software, Episode 2
ragunathjawahar
1
72
Building Robust Software (Episode 1)
ragunathjawahar
1
270
Speed as a workplace habit
ragunathjawahar
1
240
Building Robust Apps (Swift Edition)
ragunathjawahar
0
160
Re-architecture
ragunathjawahar
0
390
Other Decks in Programming
See All in Programming
リハビリmruby
kishima
1
160
I/O Extended Android in Korea 2024 ~ Whats new in Android development tools
pluu
0
250
MIERUNE BBQにおけるユーザー中心設計()
mierune
PRO
1
110
ピグパーティにおけるMongoDB CommunityバージョンからAtlasへの移行事例
10969hotaka
0
130
AHC035解説
terryu16
0
710
Folding Cheat Sheet #7
philipschwarz
PRO
0
150
開発部に不満を持っていたCSがエンジニアにジョブチェンしてわかった「勝手に諦めない」ことの大切さ
sakuraikotone
28
16k
Composing an API the *right* way (Droidcon Berlin 2024)
zsmb
1
450
DynamoDB コスト最適化っぽいことの基本 with Terraform
kuro_kurorrr
2
250
大規模マルチテナントを解決するYugabyteDBという選択肢
nnaka2992
1
250
DDDを志して3年経ったら「DDDの皮を被ったクリーンアーキテクチャ」になった話【デブサミ2024夏】
texmeijin
1
620
CSC307 Lecture 09
javiergs
PRO
1
500
Featured
See All Featured
Become a Pro
speakerdeck
PRO
15
4.8k
Infographics Made Easy
chrislema
238
18k
Agile that works and the tools we love
rasmusluckow
325
20k
For a Future-Friendly Web
brad_frost
173
9.2k
"I'm Feeling Lucky" - Building Great Search Experiences for Today's Users (#IAC19)
danielanewman
224
21k
The Brand Is Dead. Long Live the Brand.
mthomps
52
36k
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
16
1.6k
The Invisible Side of Design
smashingmag
294
50k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
23
1.9k
How STYLIGHT went responsive
nonsquared
93
5k
Put a Button on it: Removing Barriers to Going Fast.
kastner
58
3.3k
Why You Should Never Use an ORM
jnunemaker
PRO
51
8.9k
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