PPrepLearn
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:

  • ViewModel survives rotation (unlike Activity).
  • LiveData/StateFlow are lifecycle-aware.
  • Separation makes testing easy (ViewModel doesn't need Android context).
View ← observes ← ViewModel ← calls β†’ Repository β†’ DataSource
What is ViewModel and why is it important?

ViewModel is a Jetpack component that:

  • Survives configuration changes (screen rotation).
  • Holds StateFlow/LiveData for 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
LiveDataStateFlow
Lifecycle-awareYes (built-in)Requires collectAsStateWithLifecycle
Thread-safeYes (postValue)Yes (Kotlin coroutines)
Transformationmap, switchMapFlow operators
Kotlin-firstNoYes
Null supportYesRequires 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 UseCase classes 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.

Related