Android ViewModel, StateFlow, SharedFlow & State Preservation

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.

LiveData vs. Kotlin StateFlow: The Modern Transition

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()

Complete ViewModel Implementation with StateFlow & SharedFlow

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}")
            }
        }
    }
}

Collecting StateFlow Safely in Jetpack Compose

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)
            }
        }
    }
}

Frequently Asked Questions (FAQ) - Student & Interview Review

Q1. Why should you NEVER pass a Context or View reference into a ViewModel?

Activities are frequently destroyed and recreated during screen rotation, while the ViewModel survives. If a ViewModel holds a reference to an Activity Context or View, that entire destroyed Activity cannot be garbage collected, creating a massive **Memory Leak**.

Q2. What is the difference between StateFlow and SharedFlow?

`StateFlow` is a state-holder observable that always retains its latest value (hot stream with replay=1) for driving UI state. `SharedFlow` is an event emitter (hot stream with replay=0 by default) designed for one-time transient events like showing a Snackbar or triggering screen navigation.

Q3. What is viewModelScope?

`viewModelScope` is a CoroutineScope tied directly to the lifecycle of the ViewModel. Any coroutines launched inside `viewModelScope` are automatically cancelled when the ViewModel is cleared (`onCleared()`), preventing orphaned background threads.

Explore Related Android Development Classes & Next Steps

Broaden your foundational and advanced Android application development skills across our curriculum:

Compose State Management

Collect StateFlow in Compose via collectAsStateWithLifecycle.

Coroutines & Reactive Flow

Transform and filter data streams using Kotlin Flow operators.

Clean Architecture & MVVM/MVI

Architect unidirectional state flows and event channels.

Unit Testing & UI Testing

Test ViewModel state transitions using Turbine and TestDispatcher.

Share Share on Facebook Share on Twitter Share on LinkedIn Pin on Pinterest Share on Reddit

You may also like this!