Photo by ben o'bro on Unsplash

MVVM to MVI: A Guide to Migrating Your Android Architecture

Abhishek Pathak
6 min readApr 26, 2023

Moving from the Model-View-ViewModel (MVVM) architecture to the Model-View-Intent (MVI) architecture can seem like a daunting task. But with the benefits of a cleaner architecture and more predictable app behavior, it’s a transition that can be well worth the effort.

In this article, we’ll explore the differences between MVVM and MVI, and provide guidance on how to make the migration to MVI.

MVVM vs MVI

The MVVM architecture is a widely-used pattern in mobile app development. It separates the user interface (View) from the data and logic (ViewModel), and allows for data binding between the two components. The ViewModel is responsible for preparing the data for the View and handling user input.

The MVI architecture, on the other hand, is a newer pattern that has gained popularity in recent years. It also separates the View and the data, but adds a new layer called the Intent, which is responsible for representing user actions in a structured and predictable way. The Intent is passed to the Model, which produces a new state that is then displayed in the View.

The key difference between the two architectures is that in MVVM, the ViewModel is responsible for both user actions and updating the state of the View. In MVI, the Intent is a separate layer that represents user actions, making it easier to reason about app behavior and handle edge cases.

Why MVI?

1.Better separation of concerns: With MVI, the Intent layer is responsible for user actions, the Model layer is responsible for state management, and the View layer is responsible for rendering the state. This separation makes it easier to reason about app behavior and to make changes to the app without affecting other parts of the architecture.

2. More predictable app behavior: With MVI, the state of the app is always represented by a single data object, which makes it easier to reason about and predict the behavior of the app. This predictability can lead to fewer bugs and a better user experience.

3. Improved testability: MVI makes it easier to write unit tests, as each layer can be tested independently. The predictable flow of data through the architecture makes it easier to write tests that cover all possible states and edge cases.

4. State Management Using Immutability (Single Source of Truth) :

By managing state with immutability, you ensure that your application’s state is predictable and consistent. Each state represents a snapshot of the UI at a given time, which helps avoid state-related bugs and makes debugging easier.

5. Unidirectional Data Flow: MVI enforces a unidirectional flow of data, where state changes flow in a single direction. This reduces the complexity of tracking how data moves through the application, making it easier to understand, test, and debug.

6. Reproducible State (Clear Data Flow) : Because state changes are driven by a clear sequence of actions (intents) and state transformations (reducers), you can easily reproduce any given state by replaying the same sequence of actions. This makes it simpler to reuse code across different parts of the application or even different projects.

How to migrate from MVVM to MVI?

Migrating from MVVM to MVI can seem like a daunting task, but it’s important to remember that it’s a gradual process. Here are some steps to consider:

  1. Understand the MVI architecture: Before making the switch, make sure you understand the key concepts of MVI, including the Intent layer and the unidirectional data flow.
  2. Identify the components: Take a close look at your existing MVVM code and identify the components that will need to change. This includes the ViewModel, the View, and any data models or repositories.
  3. Create the Intent layer: Add an Intent layer to your app, which represents user actions in a structured and predictable way. This layer should be separate from the ViewModel and the Model.
  4. Update the ViewModel: Modify your ViewModel to accept Intents and pass them to the Model. The ViewModel should only be responsible for updating the state of the View.
  5. Update the Model: Modify your Model to produce a new state based on the Intent and the current state. The Model no longer be responsible for handling user input.
  6. Update the View: Modify your View to render the state provided by the Model. The View should no longer be responsible for fetching data or updating the state.
  7. Write unit tests for each layer of the architecture to ensure that data flows predictably and that all possible states and edge cases are covered.

Implmentation major steps to migrate from MVVM to MVI

Step 1: Create Intent classes

sealed class MyIntent {
object LoadData : MyIntent()
data class UpdateData(val newData: Data) : MyIntent()
object DeleteData : MyIntent()
}

Step 2: Modify ViewModel

Modify your ViewModel to accept intents and pass them to the Model. The ViewModel should only be responsible for updating the state of the View.

class MyViewModel(private val myModel: MyModel) : ViewModel() {
private val _state = MutableLiveData<MyState>()
val state: LiveData<MyState> = _state

fun processIntent(intent: MyIntent) {
val newState = myModel.processIntent(intent, _state.value)
_state.postValue(newState)
}
}

Step 3: Create State class

Create a class that represents the current state of your app, for example:

class MyModel(private val myRepository: MyRepository) {
fun processIntent(intent: MyIntent, currentState: MyState?): MyState {
return when (intent) {
is MyIntent.LoadData -> {
// Load data from repository
myRepository.getData()
.map { data -> currentState?.copy(data = data) ?: MyState(data = data) }
.onStart { emit(currentState?.copy(isLoading = true) ?: MyState(isLoading = true)) }
.onErrorReturn { throwable ->
currentState?.copy(isLoading = false, error = throwable)
?: MyState(isLoading = false, error = throwable)
}
}
is MyIntent.UpdateData -> {
// Update data in repository
myRepository.updateData(intent.newData)
currentState?.copy(data = intent.newData) ?: MyState(data = intent.newData)
}
is MyIntent.DeleteData -> {
// Delete data from repository
myRepository.deleteData()
MyState()
}
}
}
}

Step 5: Modify View

Modify your View to render the state provided by the Model. The View should no longer be responsible for fetching data or updating the state.

class MyActivity : AppCompatActivity() {
private val viewModel: MyViewModel by viewModels()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_my)

viewModel.state.observe(this, Observer { state ->
// Render state in UI
when {
state.isLoading -> showLoading()
state.error != null -> showError(state.error)
state.data != null -> showData(state.data)
}
})
}

private fun showLoading() {
// Show loading spinner or progress bar
}

private fun showError(error: Throwable) {
// Show error message
}

private fun showData(data: Data) {
// Show data in UI
}
}

Conclusion

In conclusion, migrating from MVVM to MVI can offer several benefits for your Android app. By adopting the MVI architecture, you can create a more reactive, predictable, and testable app, while reducing coupling between layers and improving the overall maintainability of your codebase.

MVI allows you to model user interactions as intents, update the state of your app based on those intents, and then render that state in the UI. This makes it easier to reason about the flow of data through your app, and to make changes to your code without introducing unintended side effects. Additionally, MVI helps to avoid common issues in MVVM such as “callback hell” and “state explosion”.

Of course, there are some challenges to migrating from MVVM to MVI, such as the need to refactor existing code and to learn new concepts and patterns. However, with careful planning, clear documentation, and thorough testing, the benefits of MVI can far outweigh the costs. By adopting a reactive and predictable architecture like MVI, you can create more reliable, scalable, and maintainable Android apps that meet the needs of your users and stakeholders.

Thank you for reading my article. I really appreciate your response.

Clap if this article helps you. If I got something wrong, please comment for improve.
let’s connect on
Linkedin , GitHub

--

--