The ViewModel class is an Android Architecture Component designed to store and manage UI-related data in a lifecycle-conscious manner. The ViewModel allows data to survive configuration changes such as screen rotations, eliminating the need to re-fetch expensive network data when the user flips their smartphone.
Historically, LiveData was the standard observable data holder. In modern Kotlin-first Android development, StateFlow from kotlinx.coroutines has completely replaced LiveData:
| Dimension | Legacy LiveData (Java Era) | Modern StateFlow (Coroutines Era) |
|---|---|---|
| Platform Dependency | Bound to Android SDK (cannot be used in pure Kotlin/KMP) | Pure Kotlin Coroutines (100% portable to Kotlin Multiplatform) |
| Initial Value | Nullable; starts empty without initial value | Requires an initial value (always holds valid state) |
| Threading & Transformations | Limited operators (map, switchMap) |
Full power of reactive Flow operators (filter, debounce, combine) |
| Compose Integration | Requires observeAsState() |
Native collectAsStateWithLifecycle() |
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
data class ArticlesUiState(
val articles: List<String> = emptyList(),
val isLoading: Boolean = false
)
class ArticlesViewModel(
private val repository: ArticleRepository
) : ViewModel() {
// 1. Backing property for State (Private Mutable, Public Immutable)
private val _uiState = MutableStateFlow(ArticlesUiState(isLoading = true))
val uiState: StateFlow<ArticlesUiState> = _uiState.asStateFlow()
// 2. SharedFlow for One-Time Side Effects (Snackbars, Toast, Navigation)
private val _eventFlow = MutableSharedFlow<String>()
val eventFlow: SharedFlow<String> = _eventFlow.asSharedFlow()
init {
loadArticles()
}
fun loadArticles() {
viewModelScope.launch {
_uiState.update { it.copy(isLoading = true) }
try {
val data = repository.fetchArticles()
_uiState.update { it.copy(articles = data, isLoading = false) }
} catch (e: Exception) {
_uiState.update { it.copy(isLoading = false) }
_eventFlow.emit("Failed to load articles: ${e.message}")
}
}
}
}
In Compose, collect StateFlow using collectAsStateWithLifecycle() to automatically stop collecting when the app enters the background, saving precious CPU cycles:
import androidx.lifecycle.compose.collectAsStateWithLifecycle
@Composable
fun ArticlesScreen(viewModel: ArticlesViewModel) {
val state by viewModel.uiState.collectAsStateWithLifecycle()
if (state.isLoading) {
CircularProgressIndicator()
} else {
LazyColumn {
items(state.articles) { article ->
Text(article)
}
}
}
}
Broaden your foundational and advanced Android application development skills across our curriculum:
Collect StateFlow in Compose via collectAsStateWithLifecycle.
Transform and filter data streams using Kotlin Flow operators.
Architect unidirectional state flows and event channels.
Test ViewModel state transitions using Turbine and TestDispatcher.