SyntaxStudy
Sign Up
Kotlin ViewModel and StateFlow
Kotlin Beginner 1 min read

ViewModel and StateFlow

The ViewModel class from Jetpack Architecture Components survives configuration changes such as screen rotations. Business logic and UI state belong in the ViewModel, keeping Activities and Fragments as thin view controllers that observe and react to state. StateFlow is the modern alternative to LiveData for ViewModel state exposure. It is a hot flow that always has a value, replays the latest value to new collectors, and integrates naturally with coroutines. The UI layer collects the flow inside a repeatOnLifecycle block to avoid collecting in the background. Combining ViewModel with StateFlow and coroutines produces a clean unidirectional data flow architecture: user events trigger ViewModel functions, the ViewModel updates MutableStateFlow, and the UI renders the new state.
Example
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

data class UiState(
    val isLoading: Boolean = false,
    val items: List<String> = emptyList(),
    val error: String? = null,
)

class ItemViewModel : ViewModel() {

    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()

    init { loadItems() }

    fun loadItems() {
        viewModelScope.launch {
            _uiState.update { it.copy(isLoading = true, error = null) }
            try {
                val result = repository.getItems()   // suspend fun
                _uiState.update { it.copy(isLoading = false, items = result) }
            } catch (e: Exception) {
                _uiState.update { it.copy(isLoading = false, error = e.message) }
            }
        }
    }
}

// Fragment (collector side)
// lifecycleScope.launch {
//     viewLifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
//         viewModel.uiState.collect { state ->
//             binding.progressBar.isVisible = state.isLoading
//             adapter.submitList(state.items)
//             state.error?.let { showError(it) }
//         }
//     }
// }