Progress Β· 0/10 sections
ποΈ ποΈ Architecture Patterns
3 min read Β· Notion
ποΈ MVVM
What is MVVM and why is it the standard for Android?
Model-View-ViewModel:
- Model: Data layer β repositories, data sources, domain entities.
- View: UI layer β Activity/Fragment/Composable. Observes ViewModel state.
- ViewModel: Holds and exposes UI state. Survives configuration changes. Business logic bridge.
Android-specific benefits:
ViewModelsurvives rotation (unlike Activity).LiveData/StateFloware lifecycle-aware.- Separation makes testing easy (ViewModel doesn't need Android context).
View β observes β ViewModel β calls β Repository β DataSourceWhat is ViewModel and why is it important?
ViewModel is a Jetpack component that:
- Survives configuration changes (screen rotation).
- Holds
StateFlow/LiveDatafor UI state. - Scoped to a lifecycle (Activity/Fragment/NavGraph).
class UserViewModel(private val repo: UserRepository) : ViewModel() {
private val _uiState = MutableStateFlow<UserState>(UserState.Loading)
val uiState: StateFlow<UserState> = _uiState
fun loadUser(id: Int) = viewModelScope.launch {
_uiState.value = UserState.Success(repo.getUser(id))
}
}LiveData vs StateFlow
LiveData | StateFlow | |
|---|---|---|
| Lifecycle-aware | Yes (built-in) | Requires collectAsStateWithLifecycle |
| Thread-safe | Yes (postValue) | Yes (Kotlin coroutines) |
| Transformation | map, switchMap | Flow operators |
| Kotlin-first | No | Yes |
| Null support | Yes | Requires nullable type |
Prefer StateFlow for new code; it integrates better with coroutines and Compose.
π§± Clean Architecture
What is Clean Architecture in Android?
Clean Architecture separates code into concentric layers with a strict dependency rule: outer layers depend on inner layers, never the reverse.
Presentation (ViewModel, Composables)
β
Domain (UseCases, Entities, Repository interfaces)
β
Data (RepositoryImpl, DataSources, APIs, DB)- Domain layer: Pure Kotlin. No Android dependencies. Holds
UseCaseclasses and repository interfaces. - Data layer: Implements repository interfaces. Contains Retrofit/Room.
- Presentation layer: ViewModels, UI components.
What is a UseCase?
A UseCase (also called Interactor) encapsulates a single business operation:
class GetUserUseCase(private val repo: UserRepository) {
suspend operator fun invoke(id: Int): User = repo.getUser(id)
}
// In ViewModel:
val user = getUserUseCase(userId)Benefits: Reusable across ViewModels, easy to test, clear single responsibility.
Repository pattern
The repository abstracts data sources behind a single interface:
interface UserRepository {
suspend fun getUser(id: Int): User
}
class UserRepositoryImpl(
private val api: UserApi,
private val dao: UserDao
) : UserRepository {
override suspend fun getUser(id: Int): User {
return dao.getUser(id) ?: api.getUser(id).also { dao.insert(it) }
}
}ViewModel/UseCase only knows about the interface β not whether data comes from network or cache.
SOLID principles in Android
- Single Responsibility: Each class does one thing (ViewModel β data fetcher).
- Open/Closed: Extend behavior via new classes, not modifying existing ones.
- Liskov Substitution: Implementations can replace their interfaces without breaking behavior.
- Interface Segregation: Prefer small, focused interfaces.
- Dependency Inversion: Depend on abstractions (interfaces), not concrete classes.
π MVI
What is MVI architecture?
Model-View-Intent:
- Model: Immutable UI state.
- View: Renders state, emits user intents.
- Intent: User actions that trigger state transitions.
sealed class UserIntent {
data class LoadUser(val id: Int) : UserIntent()
object Retry : UserIntent()
}
data class UserState(
val isLoading: Boolean = false,
val user: User? = null,
val error: String? = null
)MVI pairs naturally with StateFlow and Compose because both are state-driven. The unidirectional data flow makes state transitions predictable and easy to test.