SlideShare uma empresa Scribd logo
1 de 42
Baixar para ler offline
MODEL VIEW INTENT
Zeyad Gasser

Android Engineer
MANAGING STATE

THE KOTLIN WAY
github.com/zeyad-37
HISTORY: MV/C/P/VM
MVC
MVC
PROBLEMS
▸ Not reactive in nature
▸ A lot of developers don’t agree which is the View and which is the Controller
▸ Ending up with God Controllers was easy
▸ Hard to test
MVP
MVP
PROBLEMS
▸ Call back hell
▸ Needs a reference to our View
MVVM
MVVM
PROBLEMS
▸ Observable lifecycle management hell
▸ Inconsistent states
▸ Irreproducible bugs
MVI / REDUX
MVI?
▸ Model View Intent architecture is a pattern that is reactive, event based,
immutable and managed as a state machine.
▸ Model: Is the current state of your view
▸ View: is the view!
▸ Intent: Events that eventually reduces our Model to new Models or States
WHY?
MVI
WHY?
▸ Reactive
▸ Functional
▸ Unidirectional Data Flow
▸ Immutable States
▸ Single Source of Truth
▸ Acts as documentation
▸ Easy to test and debug (Time Travel bonus)
▸ Better integration with designers
▸ Kotlin friendly
HOW?
MVI
HOW? 5 STEPS
▸ Define an initial state
▸ Events from the User are mapped to actions -> Sealed Classes
▸ Actions are mapped to Results -> Sealed Classes
▸ Results + Old State = New State -> State Machine
▸ Bind new state to view -> Data-binding
SHOW ME THE CODE
SHOW ME THE CODE
DATA CLASSES
▸ BaseEvent<T>
▸ Result<S>
▸ UIModel<S>
SHOW ME THE CODE
BASEEVENT<T>
interface BaseEvent<T> {
fun getPayLoad(): T
}
SHOW ME THE CODE
BASEEVENT<T>
interface BaseEvent<T> {
fun getPayLoad(): T
}
RESULT<S>
sealed class Result<S> {
abstract val event: BaseEvent<*>
}
data class LoadingResult(override val event: BaseEvent<*>) : Result<Nothing>()
data class ErrorResult(val error: Throwable,
override val event: BaseEvent<*>) : Result<Nothing>()
data class SuccessResult<S>(val bundle: S,
override val event: BaseEvent<*>) : Result<S>()
SHOW ME THE CODE
UIMODEL<S>
sealed class UIModel<S> {
abstract val event: BaseEvent<*>
abstract val bundle: S
override fun toString() = "stateEvent: $event"
}
data class LoadingState<S>(override val bundle: S,
override val event: BaseEvent<*>) : UIModel<S>() {
override fun toString() = "State: Loading, " + super.toString()
}
data class ErrorState<S>(val error: Throwable,
val errorMessage: String,
override val bundle: S,
override val event: BaseEvent<*>) : UIModel<S>() {
override fun toString() = "State: Error, Throwable: $error, " + super.toString()
}
data class SuccessState<S>(override val bundle: S,
override val event: BaseEvent<*> = EmptyEvent) : UIModel<S>() {
override fun toString() = "State: Success, Bundle: $bundle, " + super.toString()
}
SHOW ME THE CODE
REPRESENTING STATE IN UIMODEL<S> ??
sealed class ListState {
abstract val list: List<ItemInfo>
abstract val page: Int
abstract val callback: DiffUtil.DiffResult
}
@Parcelize

data class EmptyState(override val list: List<ItemInfo> = emptyList(),
override val page: Int = 0,
override val callback: DiffUtil.DiffResult) : ListState(), Parcelable
@Parcelize
data class GetState(override val list: List<ItemInfo> = emptyList(),
override val page: Int = 1,
override val callback: DiffUtil.DiffResult) : ListState(), Parcelable
SHOW ME THE CODE
VIEWMODEL
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>>
SHOW ME THE CODE
VIEWMODEL - SWITCH TO A BACKGROUND THREAD
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
}
SHOW ME THE CODE
VIEWMODEL - MAP EVENTS TO ACTIONS
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
}
}
fun mapEventsToActions(): Function<BaseEvent<*>, Flowable<*>> {
return Function { event ->
val userListEvent = event as UserListEvents
when (userListEvent) {
is GetPaginatedUsersEvent -> getUsers(userListEvent.getPayLoad())
is DeleteUsersEvent -> deleteCollection(userListEvent.getPayLoad())
is SearchUsersEvent -> search(userListEvent.getPayLoad())
}
}
}
SHOW ME THE CODE
VIEWMODEL - MAP ACTIONS RESPONSES TO RESULTS
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
}
SHOW ME THE CODE
VIEWMODEL - STATE MACHINE
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
.scan<UIModel<S>>(SuccessState(initialState), reducer())
}
SHOW ME THE CODE
VIEWMODEL - REDUCER
fun reducer(): BiFunction<UIModel<S>, Result<*>, UIModel<S>> =
BiFunction { currentUIModel, result ->
result.run {
when (this) {
is LoadingResult -> when (currentUIModel) {
is LoadingState ->
throw IllegalStateException(getErrorMessage(currentUIModel, this, LOADING_STATE))
is SuccessState -> LoadingState(currentUIModel.bundle, event)
is ErrorState -> LoadingState(currentUIModel.bundle, event)
}
is ErrorResult -> when (currentUIModel) {
is LoadingState -> ErrorState(currentUIModel.bundle, error, event)
is SuccessState ->
throw IllegalStateException(getErrorMessage(currentUIModel, this, SUCCESS_STATE))
is ErrorState ->
throw IllegalStateException(getErrorMessage(currentUIModel, this, ERROR_STATE))
}
is SuccessResult<*> -> when (currentUIModel) {
is SuccessState ->
SuccessState(stateReducer()
.invoke(bundle!!, event, currentUIModel.bundle), event)
is LoadingState -> SuccessState(stateReducer()
.invoke(bundle!!, event, currentUIModel.bundle), event)
is ErrorState ->
throw IllegalStateException(getErrorMessage(currentUIModel, this, ERROR_STATE))
}
}
}
}
SHOW ME THE CODE
VIEWMODEL - SUCCESS STATE REDUCER
fun stateReducer(): (newResult: Any, event: BaseEvent<*>, currentStateBundle: UserListState) -> UserListState {
return { newResult, _, currentStateBundle ->
when (currentStateBundle) {
is EmptyState -> when (newResult) {
is List<*> -> {
// Calculate your new State
GetState(pair.first, currentStateBundle.lastId, pair.second)
}
else -> throw IllegalStateException("Can not reduce EmptyState with this result: $newResult!")
}
is GetState -> when (newResult) {
is List<*> -> {
// Calculate your new State
GetState(pair.first, currentStateBundle.lastId + 1, pair.second)
}
else -> throw IllegalStateException("Can not reduce GetState with this result: $newResult!")
}
}
}
}
SHOW ME THE CODE
VIEWMODEL - SWITCH BACK TO MAIN THREAD
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
.scan<UIModel<S>>(SuccessState(initialState), reducer())
.observeOn(AndroidSchedulers.mainThread())
}
SHOW ME THE CODE
VIEWMODEL - CACHE LAST EMITTED STATE
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
.scan<UIModel<S>>(SuccessState(initialState), reducer())
.observeOn(AndroidSchedulers.mainThread())
.replay(1)
.autoConnect()
}
SHOW ME THE CODE
VIEWMODEL - CACHE LAST EMITTED STATE
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
.scan<UIModel<S>>(SuccessState(initialState), reducer())
.observeOn(AndroidSchedulers.mainThread())
.compose(ReplayingShare.instance())
}
MVI
VIEWMODEL - MIDDLEWARE
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
.scan<UIModel<S>>(SuccessState(initialState), reducer())
.observeOn(AndroidSchedulers.mainThread())
.compose(ReplayingShare.instance())
.doAfterNext {
when (it) {
is SuccessState, is LoadingState ->
Timber.d("UIModel: ${it.toString()}")
is ErrorState -> Timber.e(it.error, "UIModel")
}
// Bug reporting, etc…
}
}
MVI
VIEWMODEL - FILTER DUPLICATES
fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> {
events.toFlowable(BackpressureStrategy.BUFFER)
.observeOn(Schedulers.computation())
.concatMap {
Flowable.just(it)
.concatMap(mapEventsToActions())
.map<Result<*>> { SuccessResult(it) }
.onErrorReturn { ErrorResult(it) }
.startWith(LoadingResult())
}
.distinctUntilChanged { r1: Result<*>, r2: Result<*> -> r1 == r2 }
.scan<UIModel<S>>(SuccessState(initialState), reducer())
.distinctUntilChanged { m1: UIModel<S>, m2: UIModel<S> -> m1 == m2 }
.doAfterNext {
when (it) {
is SuccessState, is LoadingState ->
Timber.d("UIModel: ${it.toString()}")
is ErrorState -> Timber.e(it.error, "UIModel")
}
// Bug reporting, etc…
}
.observeOn(AndroidSchedulers.mainThread())
.compose(ReplayingShare.instance())
}
SHOW ME THE CODE
VIEW
override fun onStart() {
super.onStart()
viewModel.store(events(), initialState()).toLiveData()
.observe(this, Observer { uiModel: UIModel<S>? ->
uiModel?.apply {
view.toggleViews(this is LoadingState, event)
when (this) {
is ErrorState -> showError(errorMessage, event)
is SuccessState -> {
setState(bundle)
renderSuccessState(bundle)
}
}
}
})
}
SHOW ME THE CODE
BINDING SUCCESS STATE
override fun renderSuccessState(successState: UserListState) {
when (successState) {
is EmptyState -> TODO("Provide your binding here")
is GetState -> TODO("Provide your binding here")
}
}
TESTING
MVI
TESTING
▸ Unit testing:
▸ Test your Actions
▸ Test your Reductions
▸ E2E testing:
▸ Mock your Actions
▸ Fire Events in events stream using a Subject
▸ Assert that the correct states are the coming out
▸ UI Testing:
▸ Screenshot comparison
PROS & CONS
PROS & CONS
PROS
▸ Reactive
▸ Functional
▸ Predictable
▸ Unidirectional Data Flow
▸ Immutable States
▸ Single Source of Truth
▸ Acts as documentation
▸ Easy to test and debug
▸ Kotlin friendly
PROS & CONS
CONS
▸ Boilerplate
▸ Change of mindset / Steep learning curve
▸ Creation & Destruction of a lot object instances.
▸ States need to be static. No Toast messages!
QUESTIONS?
THANK YOU
MODEL VIEW INTENT
SOURCES
▸ https://jakewharton.com/the-state-of-managing-state-with-rxjava
▸ https://github.com/zeyad-37/rxredux
▸ https://github.com/kaushikgopal/movies-usf
▸ https://fragmentedpodcast.com/episodes/103
▸ https://fragmentedpodcast.com/episodes/151
▸ https://www.youtube.com/watch?v=UsuzhTlccRk
▸ https://medium.com/airbnb-engineering/introducing-mvrx-android-on-autopilot-552bca86bd0a
▸ https://speakerdeck.com/kaushikgopal/unidirectional-state-flow-patterns-a-refactoring-story

Mais conteúdo relacionado

Mais procurados

VISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLEVISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLE
Darwin Durand
 
Special Events: Beyond Custom Events
Special Events: Beyond Custom EventsSpecial Events: Beyond Custom Events
Special Events: Beyond Custom Events
Brandon Aaron
 

Mais procurados (20)

Building Apps with Flutter - Hillel Coren, Invoice Ninja
Building Apps with Flutter - Hillel Coren, Invoice NinjaBuilding Apps with Flutter - Hillel Coren, Invoice Ninja
Building Apps with Flutter - Hillel Coren, Invoice Ninja
 
Blending Culture in Twitter Client
Blending Culture in Twitter ClientBlending Culture in Twitter Client
Blending Culture in Twitter Client
 
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
VC「もしかして...」Model「私たち...」「「入れ替わってるー!?」」を前前前世から防ぐ方法
 
The Ring programming language version 1.9 book - Part 14 of 210
The Ring programming language version 1.9 book - Part 14 of 210The Ring programming language version 1.9 book - Part 14 of 210
The Ring programming language version 1.9 book - Part 14 of 210
 
The Ring programming language version 1.8 book - Part 12 of 202
The Ring programming language version 1.8 book - Part 12 of 202The Ring programming language version 1.8 book - Part 12 of 202
The Ring programming language version 1.8 book - Part 12 of 202
 
Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React Crossing platforms with JavaScript & React
Crossing platforms with JavaScript & React
 
Backbone Basics with Examples
Backbone Basics with ExamplesBackbone Basics with Examples
Backbone Basics with Examples
 
Tinkerbelles return home from their Guinness world-record attempt on Sunday
Tinkerbelles return home from their Guinness world-record attempt on SundayTinkerbelles return home from their Guinness world-record attempt on Sunday
Tinkerbelles return home from their Guinness world-record attempt on Sunday
 
Practical Event Sourcing
Practical Event SourcingPractical Event Sourcing
Practical Event Sourcing
 
Optimizing Angular Performance in Enterprise Single Page Apps
Optimizing Angular Performance in Enterprise Single Page AppsOptimizing Angular Performance in Enterprise Single Page Apps
Optimizing Angular Performance in Enterprise Single Page Apps
 
Backbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVCBackbone.js — Introduction to client-side JavaScript MVC
Backbone.js — Introduction to client-side JavaScript MVC
 
INSERCION DE REGISTROS DESDE VISUAL.NET A UNA BD DE SQL SERVER
INSERCION DE REGISTROS DESDE VISUAL.NET A UNA BD DE SQL SERVERINSERCION DE REGISTROS DESDE VISUAL.NET A UNA BD DE SQL SERVER
INSERCION DE REGISTROS DESDE VISUAL.NET A UNA BD DE SQL SERVER
 
FLTK Summer Course - Part VI - Sixth Impact - Exercises
FLTK Summer Course - Part VI - Sixth Impact - ExercisesFLTK Summer Course - Part VI - Sixth Impact - Exercises
FLTK Summer Course - Part VI - Sixth Impact - Exercises
 
Slightly Advanced Android Wear ;)
Slightly Advanced Android Wear ;)Slightly Advanced Android Wear ;)
Slightly Advanced Android Wear ;)
 
VISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLEVISUALIZAR REGISTROS EN UN JTABLE
VISUALIZAR REGISTROS EN UN JTABLE
 
ReRxSwift
ReRxSwiftReRxSwift
ReRxSwift
 
I os 15
I os 15I os 15
I os 15
 
UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기UI 모듈화로 워라밸 지키기
UI 모듈화로 워라밸 지키기
 
Special Events: Beyond Custom Events
Special Events: Beyond Custom EventsSpecial Events: Beyond Custom Events
Special Events: Beyond Custom Events
 
Model View Intent on Android
Model View Intent on AndroidModel View Intent on Android
Model View Intent on Android
 

Semelhante a MVI - Managing State The Kotlin Way

Knockout.js presentation
Knockout.js presentationKnockout.js presentation
Knockout.js presentation
Scott Messinger
 
Redux. From twitter hype to production
Redux. From twitter hype to productionRedux. From twitter hype to production
Redux. From twitter hype to production
FDConf
 
Droidcon2013 android experience lahoda
Droidcon2013 android experience lahodaDroidcon2013 android experience lahoda
Droidcon2013 android experience lahoda
Droidcon Berlin
 

Semelhante a MVI - Managing State The Kotlin Way (20)

Building Testable Reactive Apps with MVI
Building Testable Reactive Apps with MVIBuilding Testable Reactive Apps with MVI
Building Testable Reactive Apps with MVI
 
[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern[22]Efficient and Testable MVVM pattern
[22]Efficient and Testable MVVM pattern
 
The evolution of redux action creators
The evolution of redux action creatorsThe evolution of redux action creators
The evolution of redux action creators
 
Knockout.js presentation
Knockout.js presentationKnockout.js presentation
Knockout.js presentation
 
Introducing Vuex in your project
Introducing Vuex in your projectIntroducing Vuex in your project
Introducing Vuex in your project
 
State manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to VuexState manager in Vue.js, from zero to Vuex
State manager in Vue.js, from zero to Vuex
 
Redux. From twitter hype to production
Redux. From twitter hype to productionRedux. From twitter hype to production
Redux. From twitter hype to production
 
Cyclejs introduction
Cyclejs introductionCyclejs introduction
Cyclejs introduction
 
React 101
React 101React 101
React 101
 
ASP.NET MVC Internals
ASP.NET MVC InternalsASP.NET MVC Internals
ASP.NET MVC Internals
 
(PHPers Wrocław #5) How to write valuable unit test?
(PHPers Wrocław #5) How to write valuable unit test?(PHPers Wrocław #5) How to write valuable unit test?
(PHPers Wrocław #5) How to write valuable unit test?
 
Redux. From twitter hype to production
Redux. From twitter hype to productionRedux. From twitter hype to production
Redux. From twitter hype to production
 
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, ClimacellLiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
LiveData on Steroids - Giora Shevach + Shahar Ben Moshe, Climacell
 
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
Does testability imply good design - Andrzej Jóźwiak - TomTom Dev Day 2022
 
Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016Redux with angular 2 - workshop 2016
Redux with angular 2 - workshop 2016
 
Improving android experience for both users and developers
Improving android experience for both users and developersImproving android experience for both users and developers
Improving android experience for both users and developers
 
Droidcon2013 android experience lahoda
Droidcon2013 android experience lahodaDroidcon2013 android experience lahoda
Droidcon2013 android experience lahoda
 
Prescribing RX Responsibly
Prescribing RX ResponsiblyPrescribing RX Responsibly
Prescribing RX Responsibly
 
React state managmenet with Redux
React state managmenet with ReduxReact state managmenet with Redux
React state managmenet with Redux
 
React lecture
React lectureReact lecture
React lecture
 

Último

Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak HamilCara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Kandungan 087776558899
 
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments""Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
mphochane1998
 

Último (20)

Computer Lecture 01.pptxIntroduction to Computers
Computer Lecture 01.pptxIntroduction to ComputersComputer Lecture 01.pptxIntroduction to Computers
Computer Lecture 01.pptxIntroduction to Computers
 
Navigating Complexity: The Role of Trusted Partners and VIAS3D in Dassault Sy...
Navigating Complexity: The Role of Trusted Partners and VIAS3D in Dassault Sy...Navigating Complexity: The Role of Trusted Partners and VIAS3D in Dassault Sy...
Navigating Complexity: The Role of Trusted Partners and VIAS3D in Dassault Sy...
 
Work-Permit-Receiver-in-Saudi-Aramco.pptx
Work-Permit-Receiver-in-Saudi-Aramco.pptxWork-Permit-Receiver-in-Saudi-Aramco.pptx
Work-Permit-Receiver-in-Saudi-Aramco.pptx
 
A Study of Urban Area Plan for Pabna Municipality
A Study of Urban Area Plan for Pabna MunicipalityA Study of Urban Area Plan for Pabna Municipality
A Study of Urban Area Plan for Pabna Municipality
 
Online food ordering system project report.pdf
Online food ordering system project report.pdfOnline food ordering system project report.pdf
Online food ordering system project report.pdf
 
Generative AI or GenAI technology based PPT
Generative AI or GenAI technology based PPTGenerative AI or GenAI technology based PPT
Generative AI or GenAI technology based PPT
 
Air Compressor reciprocating single stage
Air Compressor reciprocating single stageAir Compressor reciprocating single stage
Air Compressor reciprocating single stage
 
Engineering Drawing focus on projection of planes
Engineering Drawing focus on projection of planesEngineering Drawing focus on projection of planes
Engineering Drawing focus on projection of planes
 
Employee leave management system project.
Employee leave management system project.Employee leave management system project.
Employee leave management system project.
 
Online electricity billing project report..pdf
Online electricity billing project report..pdfOnline electricity billing project report..pdf
Online electricity billing project report..pdf
 
A CASE STUDY ON CERAMIC INDUSTRY OF BANGLADESH.pptx
A CASE STUDY ON CERAMIC INDUSTRY OF BANGLADESH.pptxA CASE STUDY ON CERAMIC INDUSTRY OF BANGLADESH.pptx
A CASE STUDY ON CERAMIC INDUSTRY OF BANGLADESH.pptx
 
HAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKAR
HAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKARHAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKAR
HAND TOOLS USED AT ELECTRONICS WORK PRESENTED BY KOUSTAV SARKAR
 
COST-EFFETIVE and Energy Efficient BUILDINGS ptx
COST-EFFETIVE  and Energy Efficient BUILDINGS ptxCOST-EFFETIVE  and Energy Efficient BUILDINGS ptx
COST-EFFETIVE and Energy Efficient BUILDINGS ptx
 
Rums floating Omkareshwar FSPV IM_16112021.pdf
Rums floating Omkareshwar FSPV IM_16112021.pdfRums floating Omkareshwar FSPV IM_16112021.pdf
Rums floating Omkareshwar FSPV IM_16112021.pdf
 
DC MACHINE-Motoring and generation, Armature circuit equation
DC MACHINE-Motoring and generation, Armature circuit equationDC MACHINE-Motoring and generation, Armature circuit equation
DC MACHINE-Motoring and generation, Armature circuit equation
 
Bridge Jacking Design Sample Calculation.pptx
Bridge Jacking Design Sample Calculation.pptxBridge Jacking Design Sample Calculation.pptx
Bridge Jacking Design Sample Calculation.pptx
 
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak HamilCara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
Cara Menggugurkan Sperma Yang Masuk Rahim Biyar Tidak Hamil
 
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments""Lesotho Leaps Forward: A Chronicle of Transformative Developments"
"Lesotho Leaps Forward: A Chronicle of Transformative Developments"
 
Computer Networks Basics of Network Devices
Computer Networks  Basics of Network DevicesComputer Networks  Basics of Network Devices
Computer Networks Basics of Network Devices
 
HOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptx
HOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptxHOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptx
HOA1&2 - Module 3 - PREHISTORCI ARCHITECTURE OF KERALA.pptx
 

MVI - Managing State The Kotlin Way

  • 1. MODEL VIEW INTENT Zeyad Gasser
 Android Engineer MANAGING STATE
 THE KOTLIN WAY github.com/zeyad-37
  • 3. MVC
  • 4. MVC PROBLEMS ▸ Not reactive in nature ▸ A lot of developers don’t agree which is the View and which is the Controller ▸ Ending up with God Controllers was easy ▸ Hard to test
  • 5. MVP
  • 6. MVP PROBLEMS ▸ Call back hell ▸ Needs a reference to our View
  • 8. MVVM PROBLEMS ▸ Observable lifecycle management hell ▸ Inconsistent states ▸ Irreproducible bugs
  • 10. MVI? ▸ Model View Intent architecture is a pattern that is reactive, event based, immutable and managed as a state machine. ▸ Model: Is the current state of your view ▸ View: is the view! ▸ Intent: Events that eventually reduces our Model to new Models or States
  • 11. WHY?
  • 12. MVI WHY? ▸ Reactive ▸ Functional ▸ Unidirectional Data Flow ▸ Immutable States ▸ Single Source of Truth ▸ Acts as documentation ▸ Easy to test and debug (Time Travel bonus) ▸ Better integration with designers ▸ Kotlin friendly
  • 13. HOW?
  • 14. MVI HOW? 5 STEPS ▸ Define an initial state ▸ Events from the User are mapped to actions -> Sealed Classes ▸ Actions are mapped to Results -> Sealed Classes ▸ Results + Old State = New State -> State Machine ▸ Bind new state to view -> Data-binding
  • 15. SHOW ME THE CODE
  • 16. SHOW ME THE CODE DATA CLASSES ▸ BaseEvent<T> ▸ Result<S> ▸ UIModel<S>
  • 17. SHOW ME THE CODE BASEEVENT<T> interface BaseEvent<T> { fun getPayLoad(): T }
  • 18. SHOW ME THE CODE BASEEVENT<T> interface BaseEvent<T> { fun getPayLoad(): T } RESULT<S> sealed class Result<S> { abstract val event: BaseEvent<*> } data class LoadingResult(override val event: BaseEvent<*>) : Result<Nothing>() data class ErrorResult(val error: Throwable, override val event: BaseEvent<*>) : Result<Nothing>() data class SuccessResult<S>(val bundle: S, override val event: BaseEvent<*>) : Result<S>()
  • 19. SHOW ME THE CODE UIMODEL<S> sealed class UIModel<S> { abstract val event: BaseEvent<*> abstract val bundle: S override fun toString() = "stateEvent: $event" } data class LoadingState<S>(override val bundle: S, override val event: BaseEvent<*>) : UIModel<S>() { override fun toString() = "State: Loading, " + super.toString() } data class ErrorState<S>(val error: Throwable, val errorMessage: String, override val bundle: S, override val event: BaseEvent<*>) : UIModel<S>() { override fun toString() = "State: Error, Throwable: $error, " + super.toString() } data class SuccessState<S>(override val bundle: S, override val event: BaseEvent<*> = EmptyEvent) : UIModel<S>() { override fun toString() = "State: Success, Bundle: $bundle, " + super.toString() }
  • 20. SHOW ME THE CODE REPRESENTING STATE IN UIMODEL<S> ?? sealed class ListState { abstract val list: List<ItemInfo> abstract val page: Int abstract val callback: DiffUtil.DiffResult } @Parcelize
 data class EmptyState(override val list: List<ItemInfo> = emptyList(), override val page: Int = 0, override val callback: DiffUtil.DiffResult) : ListState(), Parcelable @Parcelize data class GetState(override val list: List<ItemInfo> = emptyList(), override val page: Int = 1, override val callback: DiffUtil.DiffResult) : ListState(), Parcelable
  • 21. SHOW ME THE CODE VIEWMODEL fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>>
  • 22. SHOW ME THE CODE VIEWMODEL - SWITCH TO A BACKGROUND THREAD fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) }
  • 23. SHOW ME THE CODE VIEWMODEL - MAP EVENTS TO ACTIONS fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) } } fun mapEventsToActions(): Function<BaseEvent<*>, Flowable<*>> { return Function { event -> val userListEvent = event as UserListEvents when (userListEvent) { is GetPaginatedUsersEvent -> getUsers(userListEvent.getPayLoad()) is DeleteUsersEvent -> deleteCollection(userListEvent.getPayLoad()) is SearchUsersEvent -> search(userListEvent.getPayLoad()) } } }
  • 24. SHOW ME THE CODE VIEWMODEL - MAP ACTIONS RESPONSES TO RESULTS fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } }
  • 25. SHOW ME THE CODE VIEWMODEL - STATE MACHINE fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } .scan<UIModel<S>>(SuccessState(initialState), reducer()) }
  • 26. SHOW ME THE CODE VIEWMODEL - REDUCER fun reducer(): BiFunction<UIModel<S>, Result<*>, UIModel<S>> = BiFunction { currentUIModel, result -> result.run { when (this) { is LoadingResult -> when (currentUIModel) { is LoadingState -> throw IllegalStateException(getErrorMessage(currentUIModel, this, LOADING_STATE)) is SuccessState -> LoadingState(currentUIModel.bundle, event) is ErrorState -> LoadingState(currentUIModel.bundle, event) } is ErrorResult -> when (currentUIModel) { is LoadingState -> ErrorState(currentUIModel.bundle, error, event) is SuccessState -> throw IllegalStateException(getErrorMessage(currentUIModel, this, SUCCESS_STATE)) is ErrorState -> throw IllegalStateException(getErrorMessage(currentUIModel, this, ERROR_STATE)) } is SuccessResult<*> -> when (currentUIModel) { is SuccessState -> SuccessState(stateReducer() .invoke(bundle!!, event, currentUIModel.bundle), event) is LoadingState -> SuccessState(stateReducer() .invoke(bundle!!, event, currentUIModel.bundle), event) is ErrorState -> throw IllegalStateException(getErrorMessage(currentUIModel, this, ERROR_STATE)) } } } }
  • 27. SHOW ME THE CODE VIEWMODEL - SUCCESS STATE REDUCER fun stateReducer(): (newResult: Any, event: BaseEvent<*>, currentStateBundle: UserListState) -> UserListState { return { newResult, _, currentStateBundle -> when (currentStateBundle) { is EmptyState -> when (newResult) { is List<*> -> { // Calculate your new State GetState(pair.first, currentStateBundle.lastId, pair.second) } else -> throw IllegalStateException("Can not reduce EmptyState with this result: $newResult!") } is GetState -> when (newResult) { is List<*> -> { // Calculate your new State GetState(pair.first, currentStateBundle.lastId + 1, pair.second) } else -> throw IllegalStateException("Can not reduce GetState with this result: $newResult!") } } } }
  • 28. SHOW ME THE CODE VIEWMODEL - SWITCH BACK TO MAIN THREAD fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } .scan<UIModel<S>>(SuccessState(initialState), reducer()) .observeOn(AndroidSchedulers.mainThread()) }
  • 29. SHOW ME THE CODE VIEWMODEL - CACHE LAST EMITTED STATE fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } .scan<UIModel<S>>(SuccessState(initialState), reducer()) .observeOn(AndroidSchedulers.mainThread()) .replay(1) .autoConnect() }
  • 30. SHOW ME THE CODE VIEWMODEL - CACHE LAST EMITTED STATE fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } .scan<UIModel<S>>(SuccessState(initialState), reducer()) .observeOn(AndroidSchedulers.mainThread()) .compose(ReplayingShare.instance()) }
  • 31. MVI VIEWMODEL - MIDDLEWARE fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } .scan<UIModel<S>>(SuccessState(initialState), reducer()) .observeOn(AndroidSchedulers.mainThread()) .compose(ReplayingShare.instance()) .doAfterNext { when (it) { is SuccessState, is LoadingState -> Timber.d("UIModel: ${it.toString()}") is ErrorState -> Timber.e(it.error, "UIModel") } // Bug reporting, etc… } }
  • 32. MVI VIEWMODEL - FILTER DUPLICATES fun store(events: Observable<BaseEvent<*>>, initialState: S): Flowable<UIModel<S>> { events.toFlowable(BackpressureStrategy.BUFFER) .observeOn(Schedulers.computation()) .concatMap { Flowable.just(it) .concatMap(mapEventsToActions()) .map<Result<*>> { SuccessResult(it) } .onErrorReturn { ErrorResult(it) } .startWith(LoadingResult()) } .distinctUntilChanged { r1: Result<*>, r2: Result<*> -> r1 == r2 } .scan<UIModel<S>>(SuccessState(initialState), reducer()) .distinctUntilChanged { m1: UIModel<S>, m2: UIModel<S> -> m1 == m2 } .doAfterNext { when (it) { is SuccessState, is LoadingState -> Timber.d("UIModel: ${it.toString()}") is ErrorState -> Timber.e(it.error, "UIModel") } // Bug reporting, etc… } .observeOn(AndroidSchedulers.mainThread()) .compose(ReplayingShare.instance()) }
  • 33. SHOW ME THE CODE VIEW override fun onStart() { super.onStart() viewModel.store(events(), initialState()).toLiveData() .observe(this, Observer { uiModel: UIModel<S>? -> uiModel?.apply { view.toggleViews(this is LoadingState, event) when (this) { is ErrorState -> showError(errorMessage, event) is SuccessState -> { setState(bundle) renderSuccessState(bundle) } } } }) }
  • 34. SHOW ME THE CODE BINDING SUCCESS STATE override fun renderSuccessState(successState: UserListState) { when (successState) { is EmptyState -> TODO("Provide your binding here") is GetState -> TODO("Provide your binding here") } }
  • 36. MVI TESTING ▸ Unit testing: ▸ Test your Actions ▸ Test your Reductions ▸ E2E testing: ▸ Mock your Actions ▸ Fire Events in events stream using a Subject ▸ Assert that the correct states are the coming out ▸ UI Testing: ▸ Screenshot comparison
  • 38. PROS & CONS PROS ▸ Reactive ▸ Functional ▸ Predictable ▸ Unidirectional Data Flow ▸ Immutable States ▸ Single Source of Truth ▸ Acts as documentation ▸ Easy to test and debug ▸ Kotlin friendly
  • 39. PROS & CONS CONS ▸ Boilerplate ▸ Change of mindset / Steep learning curve ▸ Creation & Destruction of a lot object instances. ▸ States need to be static. No Toast messages!
  • 42. MODEL VIEW INTENT SOURCES ▸ https://jakewharton.com/the-state-of-managing-state-with-rxjava ▸ https://github.com/zeyad-37/rxredux ▸ https://github.com/kaushikgopal/movies-usf ▸ https://fragmentedpodcast.com/episodes/103 ▸ https://fragmentedpodcast.com/episodes/151 ▸ https://www.youtube.com/watch?v=UsuzhTlccRk ▸ https://medium.com/airbnb-engineering/introducing-mvrx-android-on-autopilot-552bca86bd0a ▸ https://speakerdeck.com/kaushikgopal/unidirectional-state-flow-patterns-a-refactoring-story