The MVVM and MVI architectural patterns are combined into one of the best architectures, providing the perfect architecture for any Android project.

There are too many architectural patterns available, each with its own strengths and weaknesses. All of these patterns attempt to implement the same architectural fundamentals:

Separation of Concerns (SoC) : This is a design principle used to divide a computer program into different parts so that each part can solve a separate concern. Focus is what matters when providing a solution to a problem.

This principle is closely related to the single responsibility principle of object-oriented programming, which states that “each module, class, or function is responsible for a portion of the functionality provided by the software, and that responsibility should be fully encapsulated by a third party. Class, module, or function.”

Model-driven UI: Applications should be driven by a model (preferably a persistence model). Models are independent of View objects and application components, so they are not affected by the application life cycle and related issues.

Let’s take a look at a summary of some popular architectural patterns:

MVC architecture

Trygve Reenskaug’s model-view-controller architecture is the foundation of all modern architectural patterns. Let’s look at the responsibilities of each component defined on the Wikipedia page

  • The model is responsible for managing the application’s data. It receives user input from the controller.
  • View representation represents a model in a specific format.
  • The controller responds to user input and performs interactions on data model objects. The controller receives input, optionally validates it, and then puts the input into the model.

Thus, the model is responsible for representing the state, structure, and behavior of the view, while the view represents only the model.

The MVVM architecture

In the Model-view-ViewModel architecture, the View has an instance of the ViewModel and it calls the corresponding function based on user input/action. In addition, the view looks at the different observable properties of the ViewModel to make changes. The ViewModel processes user input based on business logic and modifies the respective observable properties.

MVI architecture

In the Model-view-Intent architecture, views expose View events (user inputs/actions) and observe the Model to understand changes in View state. We process view events, translate them into their respective intentions, and pass them to the model. The model layer uses intent and Previous View-state to create a new immutable view state. Therefore, this approach follows the principle of one-way data flow, that is, data flows in only one direction: View > Intention > Model > View.

In summary, the best part of the MVVM architecture is the ViewModel, but I don’t think it follows the concept of a model as defined in the MVC pattern, because in MVVM, the local database is treated as a model, and the view observes multiple observable properties of state changes from the ViewModel. Views are not directly model-driven. In addition, these multiple observable properties of the ViewModel can lead to state overlap (two different states accidentally displayed).

The MVI pattern solves this problem by adding an actual “model” layer of intEnts that can observe state changes through views. Because this model is the unchanging single source of truth for the current view state, no state overlap occurs.

In the architecture below, I tried to combine the best MVVM and MVI patterns to get a better architecture for any Android project, and I abstracted as much as I could by creating base classes for the View and ViewModel.

MVI + LiveData + ViewModel

Before continuing, let’s reemphasize some basic terms of the MVI architecture: ViewState: as the name implies, this is part of the model layer, and our view watches the state of the model change. ViewState should represent the current state of the view at any given time. Therefore, this class should have all the variable content that our view depends on. Every time there is any user input/action, we will expose a modified copy of this type (to preserve the unmodified previous state). We can create this model using Kotlin’s data classes.

data <b>class</b> MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)

sealed <b>class</b> FetchStatus {
    object Fetching : FetchStatus()
    object Fetched : FetchStatus()
    object NotFetched : FetchStatus()
}
Copy the code

ViewEffect: in Android, we have certain actions that are more like once and for all, such as Toast, in which case we can’t use ViewState to maintain state. This means that if we display Toast with ViewState, it will be displayed again when configuration changes or every time a new state appears, unless and until we reset its state by passing the “Toast show” event. Also, if you don’t want to do this, you can use ViewEffect because it is based on SingleLiveEvent and does not maintain state. ViewEffect is also part of our model, which we can create using Kotlin’s sealed class.

sealed <b>class</b> MainViewEffect {
    data <b>class</b> ShowSnackbar(val message: String) : MainViewEffect()
    data <b>class</b> ShowToast(val message: String) : MainViewEffect()
}
Copy the code

ViewEvent: This represents all actions/events that the user can perform on the view. This is used to pass user input/actions to the ViewModel. We can create this event set using Kotlin’s sealed class.

sealed <b>class</b> MainViewEvent {
    data <b>class</b> NewsItemClicked(val newsItem: NewsItem) : MainViewEvent()
    object FabClicked : MainViewEvent()
    object OnSwipeRefresh : MainViewEvent()
    object FetchNews : MainViewEvent()
}
Copy the code

I recommend that you keep these three classes in one file, because it will give you an overview of all the viable operations and mutable content that the target view is handling.

Now, let’s take a closer look at architecture:

The figure above probably gives you the core idea of this architecture. At the heart of this architecture is the idea that we include the actual immutable model layer in the MVVM architecture, and that our views depend on this model for state changes. Thus, the ViewModel is responsible for modifying and exposing the individual model.

To avoid redundancy and simplify using this architecture in multiple places, I created two abstract classes, one for our views (activities, fragments, custom views are separate) and one for the ViewModel.

AacMviViewModel: The generic base class for creating viewModels. It requires STATE, EFFECT, and EVENT classes. We’ve seen examples of these classes above.

open <b>class</b> AacMviViewModel<STATE, EFFECT, EVENT>(application: Application) : AndroidViewModel(application), ViewModelContract<EVENT> { <b>private</b> val _viewStates: MutableLiveData<STATE> = MutableLiveData() fun viewStates(): LiveData<STATE> = _viewStates <b>private</b> <b>var</b> _viewState: STATE? = <b>null</b> <b>protected</b> <b>var</b> viewState: STATE get() = _viewState ? : <b>throw</b> UninitializedPropertyAccessException(<font>"\"viewState\" was queried before being initialized"</font><font>)
        set(value) {
            Log.d(TAG, </font><font>"setting viewState : $value"</font><font>) _viewState = value _viewStates.value = value } <b>private</b> val _viewEffects: SingleLiveEvent<EFFECT> = SingleLiveEvent() fun viewEffects(): SingleLiveEvent<EFFECT> = _viewEffects <b>private</b> <b>var</b> _viewEffect: EFFECT? = <b>null</b> <b>protected</b> <b>var</b> viewEffect: EFFECT get() = _viewEffect ? : <b>throw</b> UninitializedPropertyAccessException(</font><font>"\"viewEffect\" was queried before being initialized"</font><font>)
        set(value) {
            Log.d(TAG, </font><font>"setting viewEffect : $value"</font><font>)
            _viewEffect = value
            _viewEffects.value = value
        }

    @CallSuper
    override fun process(viewEvent: EVENT) {
        Log.d(TAG, </font><font>"processing viewEvent: $viewEvent"</font><font>)
    }
}
</font>
Copy the code

It has viewModel, renderViewState() and renderViewEffect() and we need to implement abstract features/functions. In addition, it creates a viewStateObserver, viewEffectObserver internal Livedata-observer and starts observing viewStates(), and viewEffects() onCreate() in the activity exposed by the viewmodel. Therefore, this abstract activity will perform all the actions that we must do in each activity. In addition, it records each observed viewState and viewEffect.

It is now very easy to create a new activity for this schema:

<b>class</b> MainActivity : AacMviActivity<MainViewState, MainViewEffect, MainViewEvent, MainActVM>() {
    override val viewModel: MainActVM by viewModels()

    override fun renderViewState(viewState: MainViewState) {
      <font><i>//Handle new viewState</i></font><font>
    }

    override fun renderViewEffect(viewEffect: MainViewEffect) {
      </font><font><i>//Show effects</i></font><font>
    }
}
</font>
Copy the code

That’s it, we can get everything ready and work seamlessly, recording every action and content we’re working on. States do not overlap because the model is the only source of fact for changes in view state.

Note: If you’re not familiar with this “model-driven user interface,” you might think that we’re adding more complexity than we would do directly, because for some complex views, the ViesState data class will have so many properties because each widget and its contents must have content visibility, etc. But trust me, it will make a lot of sense because it will be very easy to trace the cause of any problems/crashes.

Further improvements:

1, many people recommend using ConflatedBroadcastChannel, because it is only the latest value, but still in experimental stage, so I prefer to use LiveData. And in any case, even if we decided to use ConflatedBroadcastChannel/Flow in the future, will only need to modify the abstract class.

2. What if we have an activity and multiple fragments or custom views? How do we communicate from activity to segment, segment to segment? You are welcome to discuss in the comments section.


The last

I have been engaged in Android development for so long that I have accumulated some treasured materials and share them with you in the hope that they can help you to improve

Also share a collection of several masters together organized Android learning PDF+ architecture video + interview document + source notes, advanced architecture technology advanced brain map, Android development interview topic information, advanced architecture information

If you need, you can add Vx: 15388039515 (note: nuggets, need advanced information)

If you like this post, give me a thumbs-up, leave a comment or share your support