The Model-View-ViewModel (MVVM) and Model-View-Intent (MVI) are architectural patterns used in software development, particularly for structuring the code of user interface (UI) applications. Both patterns aim to separate concerns and manage the flow of data between the UI and the underlying data models, but they do so in different ways. Let’s explore both patterns with an example in Android using Kotlin.
MVVM (Model-View-ViewModel)
Overview:
- Model: Represents the data and business logic.
- View: Represents the UI components and displays data to the user.
- ViewModel: Acts as a mediator between the Model and the View. It retrieves data from the Model and provides it to the View. It also handles user interactions and updates the Model.
Example:
Let’s create a simple MVVM setup for a user list application.
- Model:
data class User(val id: Int, val name: String)
- ViewModel:
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
class UserViewModel : ViewModel() {
private val _users = MutableLiveData<List<User>>()
val users: LiveData<List<User>> get() = _users
init {
loadUsers()
}
private fun loadUsers() {
// Simulate loading data from a repository
_users.value = listOf(
User(1, "John Doe"),
User(2, "Jane Smith"),
User(3, "Michael Johnson")
)
}
}
- View (Activity/Fragment):
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.Observer
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
class UserActivity : AppCompatActivity() {
private val userViewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = UserAdapter()
recyclerView.adapter = adapter
userViewModel.users.observe(this, Observer { users ->
adapter.submitList(users)
})
}
}
MVI (Model-View-Intent)
Overview:
- Model: Represents the data and business logic.
- View: Represents the UI components and displays data to the user.
- Intent: Represents the user intentions or actions. In MVI, an intent typically triggers a state change that the view can react to.
Example:
Let’s create a simple MVI setup for a user list application.
- Model:
data class User(val id: Int, val name: String)
- Intent:
sealed class UserIntent {
object LoadUsers : UserIntent()
}
- ViewModel (with State):
import androidx.lifecycle.ViewModel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.update
data class UserState(val users: List<User> = emptyList(), val isLoading: Boolean = false)
class UserViewModel : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state: StateFlow<UserState> get() = _state
fun processIntent(intent: UserIntent) {
when (intent) {
UserIntent.LoadUsers -> loadUsers()
}
}
private fun loadUsers() {
_state.update { it.copy(isLoading = true) }
// Simulate loading data from a repository
val users = listOf(
User(1, "John Doe"),
User(2, "Jane Smith"),
User(3, "Michael Johnson")
)
_state.update { UserState(users = users, isLoading = false) }
}
}
- View (Activity/Fragment):
import android.os.Bundle
import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.lifecycle.lifecycleScope
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.launch
class UserActivity : AppCompatActivity() {
private val userViewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
val recyclerView: RecyclerView = findViewById(R.id.recyclerView)
recyclerView.layoutManager = LinearLayoutManager(this)
val adapter = UserAdapter()
recyclerView.adapter = adapter
lifecycleScope.launch {
userViewModel.state.collect { state ->
adapter.submitList(state.users)
// Handle loading state if needed
}
}
userViewModel.processIntent(UserIntent.LoadUsers)
}
}
Comparison
- MVVM:
- Easier to implement for simple UI updates.
- Uses LiveData to observe data changes.
- ViewModel directly updates the LiveData which is observed by the View.
- MVI:
- Uses a unidirectional data flow which can be more predictable and easier to debug.
- Uses StateFlow or other reactive streams to manage state.
- Intent-driven, meaning every action (intent) results in a new state, making state management explicit and predictable.
Choosing between MVVM and MVI largely depends on the complexity of your application and your team’s familiarity with these patterns. MVVM is often simpler for straightforward applications, while MVI can provide better scalability and maintainability for complex state management scenarios.