$30 off During Our Annual Pro Sale. View Details »

Why not MVPI or MVVMI?

ragdroid
July 14, 2018

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.

ragdroid

July 14, 2018
Tweet

More Decks by ragdroid

Other Decks in Technology

Transcript

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

    View Slide

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

    View Slide

  3. @ragdroid
    @_riteshhh
    Part 1
    What is MVI?

    View Slide

  4. @ragdroid
    @_riteshhh
    MVI

    View Slide

  5. @ragdroid
    @_riteshhh
    Model View Intent

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  9. @ragdroid
    @_riteshhh
    Model View Intent
    State

    View Slide

  10. @ragdroid
    @_riteshhh
    State
    Single Source of Truth

    View Slide

  11. @ragdroid
    @_riteshhh
    printing
    length
    completed
    noInternet
    loggedIn
    size
    scanning
    index
    State
    sPullToRefreshing
    List
    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

    View Slide

  12. @ragdroid
    @_riteshhh
    State

    View Slide

  13. @ragdroid
    @_riteshhh
    ItemsFragment
    State

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  19. @ragdroid
    @_riteshhh
    Part 2
    State Problem

    View Slide

  20. @ragdroid
    @_riteshhh

    View Slide

  21. @ragdroid
    @_riteshhh
    View Presenter Data
    User Time

    View Slide

  22. @ragdroid
    @_riteshhh
    View Presenter
    showLoading( )
    Result : [ ]
    Data
    User
    start()
    refresh()
    loadItems( )
    setRefreshing( )
    Result : [ ]
    showEmptyState( )
    hideLoading( )
    showEmptyState( )
    hideLoading( )??
    refresh( )

    View Slide

  23. @ragdroid
    @_riteshhh
    Presenter
    loadItems( )
    refresh( )
    Multiple Inputs

    View Slide

  24. @ragdroid
    @_riteshhh
    Presenter
    showEmptyState( )
    hideLoading( )
    showEmptyState( )
    hideLoading( )??
    Multiple Outputs !!!

    View Slide

  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

    View Slide

  26. @ragdroid
    @_riteshhh
    MVP -> MVI
    Part 3
    MVPI

    View Slide

  27. @ragdroid
    @_riteshhh
    MVPI

    View Slide

  28. @ragdroid
    @_riteshhh
    View Presenter Data
    MVP

    View Slide

  29. @ragdroid
    @_riteshhh
    View Presenter Data
    State {
    loading = false
    refreshing = false
    items = EMPTY
    emptyStateVisible = false
    errorMsg = “”
    }
    UIEvent
    Reducer
    newState
    Action
    Result
    oldState
    MVPI

    View Slide

  30. @ragdroid
    @_riteshhh
    View Presenter Data
    Start
    Reducer
    newState
    LoadItems
    Loading
    Loading
    initialState
    State {
    loading = false
    refreshing = false
    items = EMPTY
    emptyStateVisible = false
    errorMsg = “”
    }

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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 = “”
    }

    View Slide

  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 = “”
    }

    View Slide

  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 = “”
    }

    View Slide

  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

    View Slide

  38. @ragdroid
    @_riteshhh
    Rules of Thumb

    View Slide

  39. @ragdroid
    @_riteshhh
    Plain Objects
    Must have a type which is not undefined
    Convey intention of user
    Action

    View Slide

  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

    View Slide

  41. @ragdroid
    @_riteshhh
    Function of previous state & result (action/ partialState)
    f(previousState, Result) = new State
    Return current state for unknown result
    Reducer

    View Slide

  42. @ragdroid
    @_riteshhh
    Code

    View Slide

  43. @ragdroid
    @_riteshhh
    interface View {
    /**
    * Expose various actions
    */
    fun pullToRefreshAction(): Observable
    fun loadingAction(): Observable
    fun render(state: State)
    }
    View

    View Slide

  44. @ragdroid
    @_riteshhh
    data class State(
    val loading: Boolean,
    val items: List,
    val loadingError: Throwable?,
    val pullToRefreshing: Boolean,
    val emptyStateVisible: Boolean)
    State

    View Slide

  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):
    Result()
    object PullToRefreshing: Result()
    object PullToRefreshEmpty: Result()
    data class PullToRefreshError(val throwable: Throwable):
    Result()
    data class PullToRefreshComplete(val characters: List):
    Result()
    }
    Result

    View Slide

  46. @ragdroid
    @_riteshhh
    fun attachView(view: View) {
    val loadingResult = loadingActionToResult()
    val pullToRefreshResult = pullToRefreshActionToResult()
    val allResultObservable =
    Observable.merge(loadingResult, pullToRefreshResult)
    subscribeToStates(allResultObservable)
    }
    Presenter

    View Slide

  47. @ragdroid
    @_riteshhh
    fun subscribeToStates(allResultObservable): Disposable {
    return allResultObservable
    .scan(State.init()) {
    reducer(previousState, result)
    }
    .subscribe {
    view.render(state)
    }
    }
    Presenter

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  51. @ragdroid
    @_riteshhh
    Part 4
    MVVMI
    MVVM + MVI

    View Slide

  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

    View Slide

  53. @ragdroid
    @_riteshhh
    MVVM vs MVP
    Presenter –– reference of a view
    ViewModel –– NO reference of a view

    View Slide

  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

    View Slide

  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

    View Slide

  56. @ragdroid
    @_riteshhh
    MVI MVVM
    <~

    View Slide

  57. @ragdroid
    @_riteshhh
    protocol ViewModel {}
    ViewModel

    View Slide

  58. @ragdroid
    @_riteshhh
    protocol ViewModel {}
    ViewModel
    class UserListViewModel: ViewModel {
    let listObservable: SignalProducer<[User]>
    let errorsObservable: SignalProducer
    let isRequestExecutingObservable: SignalProducer
    }

    View Slide

  59. @ragdroid
    @_riteshhh
    protocol ViewIntent {}
    ViewIntent

    View Slide

  60. @ragdroid
    @_riteshhh
    ViewIntent
    • Button Click
    • Pull to Refresh
    • showLoader
    • showError
    • showUsers
    User Intents Logical Intents

    View Slide

  61. @ragdroid
    @_riteshhh
    protocol ViewIntent {}
    ViewIntent
    enum UserListViewIntent: ViewIntent {
    case fetchUser
    case showLoading(Bool)
    case showUsers([User])
    case showError(Error)
    }

    View Slide

  62. @ragdroid
    @_riteshhh
    MVIController
    class MVIController {
    let viewModel: Model
    let intent: Intent
    }

    View Slide

  63. @ragdroid
    @_riteshhh
    MVIController
    Extract bindings of a ViewModel into Intents

    View Slide

  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

    View Slide

  65. @ragdroid
    @_riteshhh
    MVIController
    override func setupBindings() {

    intent.showLoading <~ viewModel.isRequestExecuting
    intent.showUsers <~ viewModel.userList
    intent.showError <~ viewModel.errors
    }

    View Slide

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

    View Slide

  67. @ragdroid
    @_riteshhh
    class ViewStateReducer {
    let reducer: (Intent, State) -> State
    }
    ViewStateReducer

    View Slide

  68. @ragdroid
    @_riteshhh
    class ViewStateReducer {
    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)
    }
    }

    View Slide

  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

    View Slide

  70. @ragdroid
    @_riteshhh
    extension ViewStateRenderer {
    func renderSetup() {
    renderView <~ mviController
    .intent
    .scan(initialState, mviController.reducer)
    }
    }
    ViewStateRenderer

    View Slide

  71. @ragdroid
    @_riteshhh
    ViewStateRenderer
    extension UserListViewController: ViewStateRenderer {
    var renderView: BindingTarget {
    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)
    }
    }
    }

    View Slide

  72. @ragdroid
    @_riteshhh
    Conclusion

    View Slide

  73. @ragdroid
    @_riteshhh
    Not just State Problem
    Orientation Changes
    Navigation & Pop Backstack
    Process Death
    Immutability
    Debuggable
    Testability
    Why MVI?
    Hannes Dorfmann MVI Series

    View Slide

  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

    View Slide

  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

    View Slide

  76. @ragdroid
    @_riteshhh
    MVPI - https://github.com/ragdroid/klayground
    MVVMI - https://github.com/riteshhgupta/mvi-ios
    Demo

    View Slide

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

    View Slide