Why Architecture Matters More Than Ever
MVVM: The Established Standard
class ProfileViewModel(
private val userRepo: UserRepository
) : ViewModel() {
private val _user = MutableStateFlow<User?>(null)
val user: StateFlow<User?> = _user.asStateFlow()
private val _isLoading = MutableStateFlow(false)
val isLoading: StateFlow<Boolean> = _isLoading.asStateFlow()
private val _error = MutableStateFlow<String?>(null)
val error: StateFlow<String?> = _error.asStateFlow()
fun loadProfile(userId: String) {
viewModelScope.launch {
_isLoading.value = true
_error.value = null
try {
_user.value = userRepo.getUser(userId)
} catch (e: Exception) {
_error.value = e.message
} finally {
_isLoading.value = false
}
}
}
}MVI: Single Source of Truth
// State: single immutable object representing the entire screen
data class ProfileState(
val user: User? = null,
val isLoading: Boolean = false,
val error: String? = null
)
// Intent: every possible user action
sealed interface ProfileIntent {
data class LoadProfile(val userId: String) : ProfileIntent
data object RetryLoad : ProfileIntent
data object DismissError : ProfileIntent
}
class ProfileViewModel(
private val userRepo: UserRepository
) : ViewModel() {
private val _state = MutableStateFlow(ProfileState())
val state: StateFlow<ProfileState> = _state.asStateFlow()
fun onIntent(intent: ProfileIntent) {
when (intent) {
is ProfileIntent.LoadProfile -> loadProfile(intent.userId)
is ProfileIntent.RetryLoad -> { /* re-trigger last load */ }
is ProfileIntent.DismissError -> {
_state.update { it.copy(error = null) }
}
}
}
private fun loadProfile(userId: String) {
viewModelScope.launch {
_state.update { it.copy(isLoading = true, error = null) }
try {
val user = userRepo.getUser(userId)
_state.update { it.copy(user = user, isLoading = false) }
} catch (e: Exception) {
_state.update {
it.copy(isLoading = false, error = e.message)
}
}
}
}
}When to Choose MVVM
When to Choose MVI
Using Both Together
| Dimension | MVVM | MVI |
|---|---|---|
| State Management | Multiple independent StateFlows | Single immutable state object |
| Learning Curve | Lower — maps intuitively to UI | Higher — requires understanding reducers |
| Debugging | Moderate — multiple streams to track | Excellent — deterministic, replayable |
| Boilerplate | Less — fewer types needed | More — Intent, State, and reducer |
| Best For | Simple screens, independent state | Complex flows, interdependent state |
| Testing | Test each stream independently | Test state transitions deterministically |
| Android Docs Support | Extensive — official pattern | Growing — community-driven |
// Composable works with either pattern
@Composable
fun ProfileScreen(viewModel: ProfileViewModel) {
// MVI style: single state object
val state by viewModel.state.collectAsStateWithLifecycle()
ProfileContent(
user = state.user,
isLoading = state.isLoading,
error = state.error,
onRetry = { viewModel.onIntent(ProfileIntent.RetryLoad) },
onDismissError = {
viewModel.onIntent(ProfileIntent.DismissError)
}
)
}Key Takeaways
- 1MVVM uses multiple independent state streams; MVI consolidates into a single immutable state object.
- 2MVVM has a lower learning curve and is well-documented in Android ecosystem.
- 3MVI provides deterministic state management and complete action traceability.
- 4Use MVVM for simple screens, MVI for complex interdependent state.
- 5Both patterns can coexist in the same app -- the boundary is at the ViewModel level.
Frequently Asked
Should I migrate from MVVM to MVI?
Only if experiencing state consistency issues. MVVM remains solid for most apps. Adopt MVI principles gradually for complex screens rather than full rewrite.
Can I use both MVVM and MVI in the same app?
Yes. Many teams use MVVM for most screens and MVI for complex features. Maintain consistent patterns within each screen.
Does MVI work with Jetpack Compose?
Yes, MVI pairs exceptionally well with Compose. Compose's recomposition aligns naturally with MVI's single state object pattern.
Ready to architect your next Android app?
ANDROID-ARCHITECT generates production-ready Kotlin code, architecture blueprints, and CI/CD configurations from plain-language descriptions. Start building for free.