Upgrade to Pro — share decks privately, control downloads, hide ads and more …

MVI

 MVI

Introduction and application of the reactive MVI architecture in the Android world.

Iveta Jurcikova

December 11, 2018
Tweet

More Decks by Iveta Jurcikova

Other Decks in Programming

Transcript

  1. WHAT IS THE “MODEL”? • Responsible for handling the business

    logic • It is where your business logic and application data is stored • It is an interface responsible for managing data • Represents the data and business logic of the app • Gateway to your domain layer or business logic 3
  2. WHAT IS THE “MODEL”? • Responsible for handling the business

    logic • It is where your business logic and application data is stored • It is an interface responsible for managing data • Represents the data and business logic of the app • Gateway to your domain layer or business logic 3
  3. • First defined by Trygve Reenskaug in 1979 • “Model

    is responsible for representing state, structure and behaviour of the user’s mental model.” MVC 5
  4. • First defined by Trygve Reenskaug in 1979 • “Model

    is responsible for representing state, structure and behaviour of the user’s mental model.” • “A View presents information that it retrieves from one or more model objects.” MVC 5
  5. • First defined by Trygve Reenskaug in 1979 • “Model

    is responsible for representing state, structure and behaviour of the user’s mental model.” • “A View presents information that it retrieves from one or more model objects.” • “Let the View register with the Model as being a dependent of the Model, and let the Model send appropriate messages to its dependents whenever it changes.” MVC 5
  6. 7

  7. 7

  8. 8

  9. 9

  10. PURE FUNCTION 23 view(model(intent()))) fun sum(a: Int, b: Int): Int

    { return a + b }} fun sum(a: Int, b: Int): Int { println(a + b) return a + b }
  11. PURE FUNCTION 24 view(model(intent()))) fun sum(a: Int, b: Int): Int

    { return a + b }} var b = 0 fun sum(a: Int): Int { return a + b }
  12. PURE FUNCTION 24 view(model(intent()))) fun sum(a: Int, b: Int): Int

    { return a + b }} var b = 0 fun sum(a: Int): Int { return a + b }
  13. STATE 29 • Immutable data structure • Single source of

    truth • intent() for each change ➡ Clear understanding of what happens in the app
  14. STATE 32 1 x 1 class CountryListViewState(( val isLoading =

    false,,, val countries = emptyList()))
  15. STATE 32 1 x 1 class CountryListViewState(( val isLoading =

    false,,, val countries = emptyList()))
  16. STATE 32 1 x 1 class CountryListViewState(( val isLoading =

    false,, , val countries = emptyList(), val isRefreshing = true))
  17. STATE 33 class CountryListViewState(( val isLoading = false,, , val

    countries = emptyList(), val isRefreshing = true))
  18. STATE 33 class CountryListViewState(( val isLoading = false,, , val

    countries = emptyList(), val isRefreshing = true))
  19. class CountryListViewState(( val isLoading = false,, , val countries =

    emptyList(), val isRefreshing = true, val error = `UnknownHostException…`)) STATE 33
  20. class CountryListViewState(( val isLoading = false,, , val countries =

    emptyList(), val isRefreshing = true, val error = `UnknownHostException…`)) STATE 33
  21. STATE PROBLEM class MyProfileViewModel {{ val progress = MutableLiveData() val

    myProfile = MutableLiveData() override fun loadProfile() {{ progress.value = true subscriptions.add( myProfileInteractor.getLoggedUser().subscribe(( object : Subscriber<UserEntity>() {{ override fun onCompleted() {{ progress.value = false }} override fun onError(e: Throwable) {{ handleError(e) }} override fun onNext(userEntity: UserEntity) {{ userInteractor.setUser(userEntity) myProfile.value = userEntity }} }} )) )) }} override fun handleEditProfileClick() {{ /***/ }} }} 35
  22. STATE PROBLEM class MyProfileViewModel {{ val progress = MutableLiveData() val

    myProfile = MutableLiveData() override fun loadProfile() {{ progress.value = true subscriptions.add( myProfileInteractor.getLoggedUser().subscribe(( object : Subscriber<UserEntity>() {{ override fun onCompleted() {{ progress.value = false }} override fun onError(e: Throwable) {{ handleError(e) }} override fun onNext(userEntity: UserEntity) {{ userInteractor.setUser(userEntity) myProfile.value = userEntity }} }} )) )) }} override fun handleEditProfileClick() {{ /***/ }} }} 35 STATE PROBLEM
  23. class MyProfileViewModel {{ val progress = MutableLiveData() val myProfile =

    MutableLiveData() override fun loadProfile() {{ progress.value = true subscriptions.add( myProfileInteractor.getLoggedUser().subscribe(( object : Subscriber<UserEntity>() {{ override fun onCompleted() {{ progress.value = false }} override fun onError(e: Throwable) {{ handleError(e) }} override fun onNext(userEntity: UserEntity) {{ userInteractor.setUser(userEntity) myProfile.value = userEntity }} }} )) )) }} override fun handleEditProfileClick() {{ /***/ }} }} 36 STATE PROBLEM
  24. 37 STATE PROBLEM class MyProfileViewModel {{ val progress = MutableLiveData()

    val myProfile = MutableLiveData() override fun loadProfile() {{ progress.value = true subscriptions.add( myProfileInteractor.getLoggedUser().subscribe(( object : Subscriber<UserEntity>() {{ override fun onCompleted() {{ progress.value = false }} override fun onError(e: Throwable) {{ handleError(e) }} override fun onNext(userEntity: UserEntity) {{ userInteractor.setUser(userEntity) myProfile.value = userEntity }} }} )) )) }} override fun handleEditProfileClick() {{ /***/ }} }}
  25. 38 STATE PROBLEM class MyProfileViewModel {{ val progress = MutableLiveData()

    val myProfile = MutableLiveData() override fun loadProfile() {{ progress.value = true subscriptions.add(( myProfileInteractor.getLoggedUser().subscribe(( object : Subscriber<UserEntity>() {{ override fun onCompleted() {{ progress.value = false }} override fun onError(e: Throwable) {{ handleError(e) }} override fun onNext(userEntity: UserEntity) {{ userInteractor.setUser(userEntity) myProfile.value = userEntity }} }} )) )) }} override fun handleEditProfileClick() {{ /***/ }} }}
  26. 39 STATE PROBLEM class MyProfileViewModel {{ val progress = MutableLiveData()

    val myProfile = MutableLiveData() override fun loadProfile() {{ progress.value = true subscriptions.add( myProfileInteractor.getLoggedUser().subscribe(( object : Subscriber<UserEntity>() {{ override fun onCompleted() {{ progress.value = false }} override fun onError(e: Throwable) {{ handleError(e) }} override fun onNext(userEntity: UserEntity) {{ userInteractor.setUser(userEntity) myProfile.value = userEntity }} }} )) )) }} override fun handleEditProfileClick() {{ /***/ }} }}
  27. 46

  28. 47 • Start the screen • Refresh the screen •

    Add country to the favourite list INTENTS
  29. 47 • Start the screen • Refresh the screen •

    Add country to the favourite list • Remove country from the favourite list INTENTS
  30. 47 • Start the screen • Refresh the screen •

    Add country to the favourite list • Remove country from the favourite list • Apply filters INTENTS
  31. 48 INTENTS sealed class CountryListIntent {{ object InitialIntent : CountryListIntent()

    object SwipeToRefreshIntent : CountryListIntent() data class AddToFavoriteIntent( val countryName: String) : CountryListIntent() data class RemoveFromFavoriteIntent( val countryName: String) : CountryListIntent() data class ChangeFilterIntent( val filterType: FilterType) : CountryListIntent() }}
  32. 48 INTENTS sealed class CountryListIntent {{ object InitialIntent : CountryListIntent()

    object SwipeToRefreshIntent : CountryListIntent() data class AddToFavoriteIntent( val countryName: String) : CountryListIntent() data class RemoveFromFavoriteIntent( val countryName: String) : CountryListIntent() data class ChangeFilterIntent( val filterType: FilterType) : CountryListIntent() }}
  33. 49

  34. 49

  35. class CountryListFragment {{ fun intents() = initialIntent private val initialIntent

    by lazy {{ Observable.just(CountryListIntent.InitialIntent)) }} }} 51
  36. class CountryListFragment {{ fun intents() = Observable.merge(( initialIntent, swipeToRefreshIntent ))

    private val swipeToRefreshIntent by lazy { binding.swiperefresh.refreshes() .map { SwipeToRefreshIntent } }} }} 52
  37. 53 class CountryListFragment {{ fun intents() = Observable.merge(( initialIntent, swipeToRefreshIntent,

    addToFavoriteIntent, removeFromFavoriteIntent ).mergeWith(changeFilterIntent)} }}}
  38. 55 16 x 9 / 1 x 1 Intent Action

    USER INTERFACE VIEW MODEL intent() actionFromIntent()
  39. • Load countries • Add country to the favorite list

    • Remove country from the favorite list 57 ACTIONS
  40. sealed class { data class ( val isRefreshing: Boolean, val

    filterType: FilterType? ) : CountryListAction() data class ( val countryName: String ) : CountryListAction() data class ( val countryName: String ) : CountryListAction() } RemoveFromFavoriteAction AddToFavoriteAction 58 LoadCountriesAction CountryListAction ACTIONS
  41. class CountryListViewModel {{ val actions: Observable<CountryListViewState> = intentsSubject .map(this::actionFromIntent) fun

    actionFromIntent(intent: CountryListIntent): = when (intent) {{ is InitialIntent -> (false) is SwipeToRefreshIntent -> (true) is ChangeFilterIntent -> (intent.filterType) is AddToFavoriteIntent -> (intent.countryName) is RemoveFromFavoriteIntent -> (intent.countryName) }} }} RemoveFromFavoriteAction AddToFavoriteAction 59 LoadCountriesAction LoadCountriesAction LoadCountriesAction CountryListAction ACTIONS
  42. class CountryListViewModel {{ val actions: Observable<CountryListViewState> = intentsSubject .map(this::actionFromIntent) fun

    actionFromIntent(intent: CountryListIntent): = when (intent) {{ is InitialIntent -> (false) is SwipeToRefreshIntent -> (true) is ChangeFilterIntent -> (intent.filterType) is AddToFavoriteIntent -> (intent.countryName) is RemoveFromFavoriteIntent -> (intent.countryName) }} }} RemoveFromFavoriteAction AddToFavoriteAction 60 LoadCountriesAction LoadCountriesAction LoadCountriesAction CountryListAction ACTIONS
  43. class CountryListViewModel {{ val actions: Observable<CountryListViewState> = intentsSubject .map(this::actionFromIntent) fun

    actionFromIntent(intent: CountryListIntent): = when (intent) {{ is InitialIntent -> (false) is SwipeToRefreshIntent -> (true) is ChangeFilterIntent -> (intent.filterType) is AddToFavoriteIntent -> (intent.countryName) is RemoveFromFavoriteIntent -> (intent.countryName) }} }} RemoveFromFavoriteAction AddToFavoriteAction 61 LoadCountriesAction LoadCountriesAction LoadCountriesAction CountryListAction ACTIONS
  44. class CountryListViewModel {{ val actions: Observable<CountryListViewState> = intentsSubject .map(this::actionFromIntent) fun

    actionFromIntent(intent: CountryListIntent): = when (intent) {{ is InitialIntent -> (false) is SwipeToRefreshIntent -> (true) is ChangeFilterIntent -> (intent.filterType) is AddToFavoriteIntent -> (intent.countryName) is RemoveFromFavoriteIntent -> (intent.countryName) }} }} RemoveFromFavoriteAction AddToFavoriteAction 61 LoadCountriesAction LoadCountriesAction LoadCountriesAction CountryListAction ACTIONS
  45. 62 sealed class CountryListIntent { object InitialIntent : CountryListIntent() object

    SwipeToRefresh : CountryListIntent() data class ChangeFilterIntent( val filterType: FilterType): CountryListIntent() data class AddToFavoriteIntent( val countryName: String): CountryListIntent() data class RemoveFromFavoriteIntent( val countryName: String): CountryListIntent() } sealed class CountryListAction { data class LoadCountriesAction( val isRefreshing: Boolean, val filterType: FilterType) : CountryListAction() data class AddToFavoriteAction( val countryName: String) : CountryListAction() data class RemoveFromFavoriteAction( val countryName: String) : CountryListAction() } 5 3
  46. 63 16 x 9 / 1 x 1 Intent Action

    USER INTERFACE VIEW MODEL intent() actionFromIntent()
  47. 66 16 x 9 / 1 x 1 Intent Action

    USER INTERFACE VIEW MODEL intent() actionFromIntent()
  48. 66 16 x 9 / 1 x 1 Intent Action

    USER INTERFACE VIEW MODEL intent() actionFromIntent()
  49. 67 16 x 9 / 1 x 1 Intent Action

    Result USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor()
  50. 70 class CountryListInteractor { val actionProcessor = ObservableTransformer<CountryListAction, CountryListResult> {

    actions -> actions.publish { selector -> Observable.merge(( selector.ofType(LoadCountriesAction::class.java).compose( ), selector.ofType(AddToFavoriteAction::class.java).compose(addToFavorite), selector.ofType(RemoveFromFavoriteAction::class.java).compose(removeFromFavorite) )) }} }}} }} loadCountries
  51. 73 val loadCountries = ObservableTransformer<LoadCountriesAction, CountryListResult> { actions -> actions.flatMap

    { action -> countryRepository.getAllCountries() .map { countries -> LoadCountriesResult.Success(countries) } }} }}}
  52. 74 val loadCountries = ObservableTransformer<LoadCountriesAction, CountryListResult> { actions -> actions.flatMap

    { action -> countryRepository.getAllCountries() .map { countries -> LoadCountriesResult.Success(countries) } .onErrorReturn { LoadCountriesResult.Failure(it) } }} }}}
  53. 75 val loadCountries = ObservableTransformer<LoadCountriesAction, CountryListResult> { actions -> actions.flatMap

    { action -> countryRepository.getAllCountries() .map { countries -> LoadCountriesResult.Success(countries) } .onErrorReturn { LoadCountriesResult.Failure(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) }} }}}
  54. 76 val loadCountries = ObservableTransformer<LoadCountriesAction, CountryListResult> { actions -> actions.flatMap

    { action -> countryRepository.getAllCountries() .map { countries -> LoadCountriesResult.Success(countries) } .onErrorReturn { LoadCountriesResult.Failure(it) } .subscribeOn(Schedulers.io()) .observeOn(AndroidSchedulers.mainThread()) .startWith(LoadCountriesResult.InProgress(action.isRefreshing)) }} }}}
  55. 77 16 x 9 / 1 x 1 Intent Action

    Result USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor()
  56. 77 16 x 9 / 1 x 1 Intent Action

    Result USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor()
  57. 78 16 x 9 / 1 x 1 Intent Action

    Result State USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor() reducer()
  58. REDUX • A predicable state container • Basics • State

    • Action • Reducer 79 Result Old State New State f(previousState, action) = newState
  59. REDUX • A predicable state container • Basics • State

    • Action • Reducer • Advanced 79 Result Old State New State f(previousState, action) = newState
  60. REDUX • A predicable state container • Basics • State

    • Action • Reducer • Advanced • Middleware 79 Result Old State New State f(previousState, action) = newState
  61. REDUX • A predicable state container • Basics • State

    • Action • Reducer • Advanced • Middleware 79 Result Old State New State f(previousState, action) = newState
  62. class CountryListViewModel { val reducer = BiFunction { previousState: CountryListViewState,

    result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> /***/ is LoadCountriesResult.Failure -> 80 /***/ /***/ is LoadCountriesResult.InProgress -> }} is AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }}
  63. /***/ /***/ is LoadCountriesResult.Failure -> is LoadCountriesResult.InProgress -> }} is

    AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }} class CountryListViewModel { val reducer = BiFunction { previousState: CountryListViewState, result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> {1 previousState.copy(1 isLoading = false, isRefreshing = false, countries = result.countries, error = null )1 }} 81
  64. /***/ /***/ is LoadCountriesResult.Failure -> is LoadCountriesResult.InProgress -> }} is

    AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }} class CountryListViewModel { val reducer = BiFunction { previousState: CountryListViewState, result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> {1 previousState.copy(1 isLoading = false, isRefreshing = false, countries = result.countries, error = null )1 }} 81
  65. class CountryListViewModel { val reducer = BiFunction { previousState: CountryListViewState,

    result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> {1 previousState.copy(1 isLoading = false, isRefreshing = false, countries = result.countries, error = null )1 }} {2 2previousState.copy(2 2isLoading = false, error = result.error )2 }} /***/ is LoadCountriesResult.InProgress -> }} is AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }} 82 is LoadCountriesResult.Failure ->
  66. class CountryListViewModel { val reducer = BiFunction { previousState: CountryListViewState,

    result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> {1 previousState.copy(1 isLoading = false, isRefreshing = false, countries = result.countries, error = null )1 }} {2 2previousState.copy(2 2isLoading = false, error = result.error )2 }} /***/ is LoadCountriesResult.InProgress -> }} is AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }} 82 is LoadCountriesResult.Failure ->
  67. {3 if (result.isRefreshing) {3 3previousState.copy(3 3isLoading = false, isRefreshing =

    true )31 } else {3 previousState.copy(3 isLoading = true, isRefreshing = false )32 }3 }3 83 is LoadCountriesResult.InProgress -> val reducer = BiFunction { previousState: CountryListViewState, result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> {2 2previousState.copy(2 2isLoading = false, error = result.error )2 }} is LoadCountriesResult.Failure -> }} is AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }} {1 previousState.copy(1 isLoading = false, isRefreshing = false, countries = result.countries, error = null )1 }}
  68. {3 if (result.isRefreshing) {3 3previousState.copy(3 3isLoading = false, isRefreshing =

    true )31 } else {3 previousState.copy(3 isLoading = true, isRefreshing = false )32 }3 }3 83 is LoadCountriesResult.InProgress -> val reducer = BiFunction { previousState: CountryListViewState, result: CountryListResult -> when (result) {{ is LoadCountriesResult -> when (result) {{ is LoadCountriesResult.Success -> {2 2previousState.copy(2 2isLoading = false, error = result.error )2 }} is LoadCountriesResult.Failure -> }} is AddToFavoriteResult -> /***/ is RemoveFromFavoriteResult -> /***/ }} }} }} {1 previousState.copy(1 isLoading = false, isRefreshing = false, countries = result.countries, error = null )1 }}
  69. 84 16 x 9 / 1 x 1 Intent Action

    Result State USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor() reducer()
  70. 84 16 x 9 / 1 x 1 Intent Action

    Result State USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor() reducer()
  71. 85 16 x 9 / 1 x 1 Intent Action

    Result State USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor() render() reducer()
  72. 86 } } } class CountryListFragment { fun render(state: CountryListViewState)

    { if (state.message != null) { showMessage(state.message) }} if (state.error != null) { showErrorMessage(state.error) binding.progress.visibility = if (state.isLoading) View.VISIBLE else View.GONE binding.tvEmpty.visibility = if (!state.isLoading && state.countries.isEmpty()) View.VISIBLE else View.GONE binding.swiperefresh.isRefreshing = state.isRefreshing (binding.rvCountries.adapter as ListAdapter<Country, CountryViewHolder>) .submitList(state.countries)
  73. if (state.message != null) { showMessage(state.message) }} if (state.error !=

    null) { showErrorMessage(state.error) 87 } } } class CountryListFragment { fun render(state: CountryListViewState) { binding.model = state
  74. 88 16 x 9 / 1 x 1 Intent Action

    Result State USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor() render() reducer()
  75. 88 16 x 9 / 1 x 1 Intent Action

    Result State USER INTERFACE VIEW MODEL DATABASE/API intent() actionFromIntent() actionProcessor() render() reducer()
  76. 92 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor)
  77. 92 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor)
  78. 93 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor) .scan(CountryListViewState.idle(), reducer)
  79. 93 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor) .scan(CountryListViewState.idle(), reducer)
  80. 94 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor) .scan(CountryListViewState.idle(), reducer) .subscribe { viewState -> render(viewState) }
  81. 94 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor) .scan(CountryListViewState.idle(), reducer) .subscribe { viewState -> render(viewState) } Unidirectional data flow
  82. 94 Intent Action Result State DATABASE/API intents() .map { intent

    -> actionFromIntent(intent) } .compose(countryListInteractor.actionProcessor) .scan(CountryListViewState.idle(), reducer) .subscribe { viewState -> render(viewState) } Unidirectional data flow More predictable Easier to understand
  83. LIBRARIES • Mobius - Spotify • MvRx - Airbnb •

    StateMachine - Tinder • MVICore - Badoo • RxRedux - Freeletics • Elmdroid - Inventi 97
  84. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety SUMMARY
  85. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability SUMMARY
  86. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability SUMMARY
  87. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic SUMMARY
  88. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability SUMMARY
  89. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability CONS SUMMARY
  90. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability CONS ❌ Lot of boilerplate SUMMARY
  91. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability CONS ❌ Lot of boilerplate ❌ Complexity SUMMARY
  92. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability CONS ❌ Lot of boilerplate ❌ Complexity ❌ Objects creation SUMMARY
  93. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability CONS ❌ Lot of boilerplate ❌ Complexity ❌ Objects creation ❌ SingleLiveEvents SUMMARY
  94. 98 PROS ✅ No state problem ✅ Unidirectional data flow

    ✅ Immutability ✅ Thread safety ✅ Share-ability ✅ Debuggability ✅ Decoupled logic ✅ Testability CONS ❌ Lot of boilerplate ❌ Complexity ❌ Objects creation ❌ SingleLiveEvents ❌ No google supported SUMMARY
  95. 99

  96. 100

  97. “If you like the code you have written a year

    ago, you haven’t learned enough this year.“ 101 Someone really smart
  98. 105

  99. 109

  100. REFERENCES • Benoît Quenaudon. Model-View-Intent for Android • Garima Jain.

    Why MVI? Model View Intent -- The curious case of yet another pattern • Hannes Dorfmann. Reactive apps with Model-View-Intent • Kausik Gopal. RxJava by Example - Volume 3, the Multicast Edition • Jake Wharton. Managing State with RxJava • Hannes Dorfmann. Android Software Architecture by Example • Andre Staltz. What if the user was a function? • Dan Lew. Don't break the chain: use RxJava's compose() operator. • kotlinx.coroutines • Christina Lee. Coroutines By Example • Andrea Bresolin. Kotlin coroutines vs RxJava: an initial performance test • Redux documentation 113