Why not MVPI or MVVMI?

Why not MVPI or MVVMI?

Not long ago, when we made the shift towards MVP, it was not an easy task. The cost of bringing the team to same level was not insignificant. Our current codebase is fine! Why do we then need to move to a new pattern? Are all our efforts gone in vain and should we start from scratch? I say no!

All of us are aware of many architectural patterns like MVC, MVP, MVVM, etc. It was not long ago when we had started applying MVP or MVVM to our projects. It was not an easy task to refactor existing code to adopt these patterns, plus the cost of bringing the whole team to the same level was not insignificant. These patterns brought along many advantages like separation of logic and better testing.
We are happy with where our codebase is at the moment and it’s test coverage. Why do we then need to move to a new pattern now? Are all our efforts gone in vain, now that we have a new pattern?
There have been multiple talks about MVI, so let’s take a different approach & instead of jumping directly into code, let’s answer the above questions first. Here’s the mind map :
- - We will learn about the main problem that this pattern solves : The State Problem,
What it is and how MVI solves it.
- We will also learn that MVI can also be applied on top of our existing patterns.
- MVPI : We need not completely change the style that we have already adopted. Instead we can modify our existing style to take the advantage of the reactive flow and achieve better state management.
- Implementation to kick off MVI on top of MVP
- MVVMI : Now that Kotlin is getting adopted widely, cross-platform developers are finding it even easier to switch tabs between Kotlin and Swift.
We will learn how MVI is not just a pattern for Android. - MVI on top of current MVVM implementation from the iOS land.

A3958eeb9a7f402b134c0c017d6614ee?s=128

ragdroid

July 14, 2018
Tweet

Transcript

  1. Why not MVPI or MVVMI? Garima Jain @ragdroid Ritesh Gupta

    @_riteshh
  2. @ragdroid @_riteshhh What is MVI? MVP -> MVI State Problem

    MVVM -> MVI
  3. @ragdroid @_riteshhh Part 1 What is MVI?

  4. @ragdroid @_riteshhh MVI

  5. @ragdroid @_riteshhh Model View Intent

  6. @ragdroid @_riteshhh State View Intention Model View Intent

  7. @ragdroid @_riteshhh User Intent State View State View Intention

  8. @ragdroid @_riteshhh User Intent State View Interacts Changes Updates Seen

    State View Intention
  9. @ragdroid @_riteshhh Model View Intent State

  10. @ragdroid @_riteshhh State Single Source of Truth

  11. @ragdroid @_riteshhh printing length completed noInternet loggedIn size scanning index

    State sPullToRefreshing List<Item> isButtonEnabled pageNo electedItem isChecked e r r o r M s g i s V i s i B l e i s L o a d i n g sLoadMore pen r u n n i n g active l i k e d l o c a t i o n i d l e
  12. @ragdroid @_riteshhh State

  13. @ragdroid @_riteshhh ItemsFragment State

  14. @ragdroid @_riteshhh State { loading = true items = EMPTY

    } State
  15. @ragdroid @_riteshhh State { loading = false items = EMPTY

    emptyStateVisible = true } State
  16. @ragdroid @_riteshhh State { loading = false refreshing = true

    items = EMPTY emptyStateVisible = true } State
  17. @ragdroid @_riteshhh State { loading = false refreshing = false

    items = EMPTY emptyStateVisible = false errorMsg = “No Internet” } State
  18. @ragdroid @_riteshhh State { loading = false refreshing = false

    items = List<Item> emptyStateVisible = false errorMsg = “” } State
  19. @ragdroid @_riteshhh Part 2 State Problem

  20. @ragdroid @_riteshhh

  21. @ragdroid @_riteshhh View Presenter Data User Time

  22. @ragdroid @_riteshhh View Presenter showLoading( ) Result : [ ]

    Data User start() refresh() loadItems( ) setRefreshing( ) Result : [ ] showEmptyState( ) hideLoading( ) showEmptyState( ) hideLoading( )?? refresh( )
  23. @ragdroid @_riteshhh Presenter loadItems( ) refresh( ) Multiple Inputs

  24. @ragdroid @_riteshhh Presenter showEmptyState( ) hideLoading( ) showEmptyState( ) hideLoading(

    )?? Multiple Outputs !!!
  25. @ragdroid @_riteshhh State - No Consistency Presenter, Business Logic and

    View : own states. MVI: Single Source of Truth : Hannes Model / View State MVI: Presenter has a single output : f(x) = y OR f(a,b,c) = y State Problem
  26. @ragdroid @_riteshhh MVP -> MVI Part 3 MVPI

  27. @ragdroid @_riteshhh MVPI

  28. @ragdroid @_riteshhh View Presenter Data MVP

  29. @ragdroid @_riteshhh View Presenter Data State { loading = false

    refreshing = false items = EMPTY emptyStateVisible = false errorMsg = “” } UIEvent Reducer newState Action Result oldState MVPI
  30. @ragdroid @_riteshhh View Presenter Data Start Reducer newState LoadItems Loading

    Loading initialState State { loading = false refreshing = false items = EMPTY emptyStateVisible = false errorMsg = “” }
  31. @ragdroid @_riteshhh View Presenter Data State { loading = true

    refreshing = false items = EMPTY emptyStateVisible = false errorMsg = “” } Start Reducer newState Loading initialState Loading LoadItems
  32. @ragdroid @_riteshhh View Presenter Data Reducer Empty Start Result :

    [ ] Empty oldState newState State { loading = true refreshing = false items = EMPTY emptyStateVisible = false errorMsg = “” } LoadItems
  33. @ragdroid @_riteshhh View Presenter Data State { loading = false

    refreshing = false items = EMPTY emptyStateVisible = true errorMsg = “” } Start Result : [ ] Reducer newState oldState Empty Empty LoadItems
  34. @ragdroid @_riteshhh View Presenter Data Pull To Refresh Reducer newState

    RefreshItems oldState Refreshing Refreshing State { loading = false refreshing = false items = EMPTY emptyStateVisible = true errorMsg = “” }
  35. @ragdroid @_riteshhh View Presenter Data Pull To Refresh Reducer newState

    RefreshItems oldState Refreshing Refreshing State { loading = false refreshing = true items = EMPTY emptyStateVisible = true errorMsg = “” }
  36. @ragdroid @_riteshhh View Presenter Data Pull To Refresh Reducer newState

    RefreshItems Result : [ ] oldState RefreshEmpty RefreshEmpty State { loading = false refreshing = true items = EMPTY emptyStateVisible = true errorMsg = “” }
  37. @ragdroid @_riteshhh View Presenter Data State { loading = false

    refreshing = false items = EMPTY emptyStateVisible = true errorMsg = “” } Pull To Refresh Reducer newState RefreshItems Result : [ ] oldState RefreshEmpty RefreshEmpty
  38. @ragdroid @_riteshhh Rules of Thumb

  39. @ragdroid @_riteshhh Plain Objects Must have a type which is

    not undefined Convey intention of user Action
  40. @ragdroid @_riteshhh Immutable : List / Object nothing is mutable

    Set representing the state of View Any change is triggered only by dispatching an action State
  41. @ragdroid @_riteshhh Function of previous state & result (action/ partialState)

    f(previousState, Result) = new State Return current state for unknown result Reducer
  42. @ragdroid @_riteshhh Code

  43. @ragdroid @_riteshhh interface View { /** * Expose various actions

    */ fun pullToRefreshAction(): Observable<Boolean> fun loadingAction(): Observable<Boolean> fun render(state: State) } View
  44. @ragdroid @_riteshhh data class State( val loading: Boolean, val items:

    List<Item>, val loadingError: Throwable?, val pullToRefreshing: Boolean, val emptyStateVisible: Boolean) State
  45. @ragdroid @_riteshhh sealed class Result { object Loading: Result() object

    LoadingEmpty: Result() data class LoadingError(val throwable: Throwable): Result() data class LoadingComplete(val characters: List<Item>): Result() object PullToRefreshing: Result() object PullToRefreshEmpty: Result() data class PullToRefreshError(val throwable: Throwable): Result() data class PullToRefreshComplete(val characters: List<Item>): Result() } Result
  46. @ragdroid @_riteshhh fun attachView(view: View) { val loadingResult = loadingActionToResult()

    val pullToRefreshResult = pullToRefreshActionToResult() val allResultObservable = Observable.merge(loadingResult, pullToRefreshResult) subscribeToStates(allResultObservable) } Presenter
  47. @ragdroid @_riteshhh fun subscribeToStates(allResultObservable): Disposable { return allResultObservable .scan(State.init()) {

    reducer(previousState, result) } .subscribe { view.render(state) } } Presenter
  48. @ragdroid @_riteshhh fun reducer(previousState: State, result: Result): State = when

    (result) { is Result.Loading -> previousState.copy( loading = true, loadingError = null) is Result.LoadingError -> previousState.copy( loading = false, loadingError = result.throwable) is Result.LoadingComplete -> previousState.copy( loading = false, loadingError = null, items = result.characters) is Result.LoadingEmpty -> previousState.copy( loading = false, loadingError = null, items = emptyList(), emptyStateVisible = true) ... } Presenter
  49. @ragdroid @_riteshhh fun reducer(previousState: State, result: Result): State = when

    (result) { ... is Result.PullToRefreshing -> previousState.copy( loading = false, pullToRefreshing = true, pullToRefreshError = null) is Result.PullToRefreshError -> previousState.copy( pullToRefreshing = false, pullToRefreshError = result.throwable) is Result.PullToRefreshComplete -> previousState.copy( pullToRefreshing = false, pullToRefreshError = null, items = result.characters) is Result.PullToRefreshEmpty -> previousState.copy( pullToRefreshing = false, loadingError = null, items = emptyList(), emptyStateVisible = true ) } Presenter
  50. @ragdroid @_riteshhh fun render(state: State) { binding.model = state //databinding

    when { state.pullToRefreshError != null -> return@render state.emptyStateVisible -> return@render state.loadingError != null -> { //show error return@render } else -> { //show list } } } View
  51. @ragdroid @_riteshhh Part 4 MVVMI MVVM + MVI

  52. @ragdroid @_riteshhh ViewModel It’s a model of a view It’s

    an abstraction of the UI It should have no knowledge of the UI elements
  53. @ragdroid @_riteshhh MVVM vs MVP Presenter –– reference of a

    view ViewModel –– NO reference of a view
  54. @ragdroid @_riteshhh Typical MVVM reactive.updateTable <~ viewModel.userList reactive.showError <~ viewModel.errors

    viewModel.reactive.fetchUser <~ pullToRefreshView.didRefreshSignal pullToRefreshView.reactive.isRefreshing <~ viewModel.isRequestExecuting fullScreenLoaderView.reactive.isAnimating <~ viewModel.isRequestExecuting
  55. @ragdroid @_riteshhh Binding Hell Scattered bindings of state reactive.updateTable <~

    viewModel.userList reactive.showError <~ viewModel.errors viewModel.reactive.fetchUser <~ pullToRefreshView.didRefreshSignal pullToRefreshView.reactive.isRefreshing <~ viewModel.isRequestExecuting fullScreenLoaderView.reactive.isAnimating <~ viewModel.isRequestExecuting reactive.updateTable <~ viewModel.userList reactive.showError <~ viewModel.errors viewModel.reactive.fetchUser <~ pullToRefreshView.didRefreshSignal pullToRefreshView.reactive.isRefreshing <~ viewModel.isRequestExecuting reactive.updateTable <~ viewModel.userList reactive.showError <~ viewModel.errors
  56. @ragdroid @_riteshhh MVI MVVM <~

  57. @ragdroid @_riteshhh protocol ViewModel {} ViewModel

  58. @ragdroid @_riteshhh protocol ViewModel {} ViewModel class UserListViewModel: ViewModel {

    let listObservable: SignalProducer<[User]> let errorsObservable: SignalProducer<Error> let isRequestExecutingObservable: SignalProducer<Bool> }
  59. @ragdroid @_riteshhh protocol ViewIntent {} ViewIntent

  60. @ragdroid @_riteshhh ViewIntent • Button Click • Pull to Refresh

    • showLoader • showError • showUsers User Intents Logical Intents
  61. @ragdroid @_riteshhh protocol ViewIntent {} ViewIntent enum UserListViewIntent: ViewIntent {

    case fetchUser case showLoading(Bool) case showUsers([User]) case showError(Error) }
  62. @ragdroid @_riteshhh MVIController class MVIController<Model: ViewModel, Intent: ViewIntent> { let

    viewModel: Model let intent: Intent }
  63. @ragdroid @_riteshhh MVIController Extract bindings of a ViewModel into Intents

  64. @ragdroid @_riteshhh Typical MVVM reactive.updateTable <~ viewModel.userList reactive.showError <~ viewModel.errors

    viewModel.reactive.fetchUser <~ pullToRefreshView.didRefreshSignal pullToRefreshView.reactive.isRefreshing <~ viewModel.isRequestExecuting fullScreenLoaderView.reactive.isAnimating <~ viewModel.isRequestExecuting
  65. @ragdroid @_riteshhh MVIController override func setupBindings() { 
 intent.showLoading <~

    viewModel.isRequestExecuting intent.showUsers <~ viewModel.userList intent.showError <~ viewModel.errors }
  66. @ragdroid @_riteshhh MVIController class MVIController< Model: ViewModel, Intent: ViewIntent, State:

    ViewState> { let viewModel: Model let intent = MutableProperty<Intent?>(nil) let stateReducer: ViewStateReducer<State, Intent> }
  67. @ragdroid @_riteshhh class ViewStateReducer<State: ViewState, Intent: ViewIntent> { let reducer:

    (Intent, State) -> State } ViewStateReducer
  68. @ragdroid @_riteshhh class ViewStateReducer<State: ViewState, Intent: ViewIntent> { let reducer:

    (Intent, State) -> State } ViewStateReducer let reducer = { (intent: Intent, previousState: State) -> State in switch intent { case .showUsers: return Users(users) case .showError: return Error(error) case .showLoading: return Loading(isLoading) } }
  69. @ragdroid ViewController UIEvent Reducer User Intent previous state MVVMI Data

    MviController ViewModel State { loading = true refreshing = false items = EMPTY emptyStateVisible = false errorMsg = “” } newState logical intent api/ cache
  70. @ragdroid @_riteshhh extension ViewStateRenderer { func renderSetup() { renderView <~

    mviController .intent .scan(initialState, mviController.reducer) } } ViewStateRenderer
  71. @ragdroid @_riteshhh ViewStateRenderer extension UserListViewController: ViewStateRenderer { var renderView: BindingTarget<State>

    { switch state { case .loading(let isLoading): controller.activityIndicatorView.isHidden = !isLoading case .users(let users): controller.tableview.reloadData() case .error(let error): controller.showAlert(for: error) } } }
  72. @ragdroid @_riteshhh Conclusion

  73. @ragdroid @_riteshhh Not just State Problem Orientation Changes Navigation &

    Pop Backstack Process Death Immutability Debuggable Testability Why MVI? Hannes Dorfmann MVI Series
  74. @ragdroid @_riteshhh If you model your system as pure functions,

    you will get a finite state machine - @ragdroid MVI is MVP or MVVM done right. - @_riteshhh MVI prevents us from misusing patterns like MVP or MVVM. - @_riteshhh
  75. @ragdroid @_riteshhh http://hannesdorfmann.com/android/mosby3-mvi-1 - Hannes Dorfmann http://fragmentedpodcast.com/tag/hannes-dorfmann/ - Hannes Dorfmann

    https://www.youtube.com/watch?v=0IKHxjkgop4 - Jake Wharton https://www.youtube.com/watch?v=64rQ9GKphTg - Benoit Quenaudon https://github.com/oldergod - Benoit Quenaudon https://egghead.io/courses/getting-started-with-redux - Dan Abramov Next: http://hannesdorfmann.com/android/coordinators-android References
  76. @ragdroid @_riteshhh MVPI - https://github.com/ragdroid/klayground MVVMI - https://github.com/riteshhgupta/mvi-ios Demo

  77. FUELED http://fueled.com/garima https://fueled.com/ritesh-gupta/ THANK YOU