State Production Pipeline for One-Shot and Stream Sources
State Production Pipeline for One-Shot and Stream Sources
In a UI state production pipeline, it is common to combine one-shot operations like deleting an item or fetching a single record with stream-based sources like live repository updates. The recommended strategy is to elevate one-shot results into MutableStateFlow instances and combine them with repository streams using combine(), then expose the result as a single StateFlow scoped to viewModelScope. By the end of this lesson, you will be able to:
- Explain why one-shot operations should be elevated into streams for state production.
- Use
MutableStateFlowto represent one-shot results as reactive values. - Combine one-shot and stream sources using
combine()into a unified UI state. - Scope the resulting flow to the ViewModel lifecycle using
stateIn(). - Choose the appropriate
SharingStartedstrategy for lifecycle aware collection.
Why Elevate One-Shot Operations
A one-shot operation like deleteTask() produces a single result: the task was deleted. If this result is stored as a plain boolean field in the ViewModel, the UI has no reactive way to observe it. The developer must manually notify the UI after the operation completes, which breaks the reactive contract.
By wrapping the result in a MutableStateFlow, the one-shot outcome becomes part of the same reactive pipeline as the stream-based data. The combine() operator merges both sources, and the UI collects a single StateFlow<UiState> that always reflects the latest state from all sources.
This matters because ViewModels frequently mix both types of data. A task detail screen needs a live stream of task data from the repository (stream source) and a flag indicating whether the user just deleted the task (one-shot source). Without elevating the one-shot result into a flow, the developer must manage two separate observation mechanisms, which complicates the UI and introduces opportunities for inconsistent state.
Design Strategy
Represent each one-shot result as a MutableStateFlow. Combine it with repository provided streams using combine(). Scope the output to viewModelScope using stateIn() with SharingStarted.WhileSubscribed to activate the flow only when the UI is collecting.
class TaskDetailViewModel(
private val tasksRepository: TasksRepository,
savedStateHandle: SavedStateHandle
) : ViewModel() {
private val taskId: String =
savedStateHandle["taskId"] ?: ""
private val _isTaskDeleted = MutableStateFlow(false)
private val _task =
tasksRepository.getTaskStream(taskId)
val uiState: StateFlow<TaskDetailUiState> = combine(
_isTaskDeleted,
_task
) { isDeleted, taskResult ->
TaskDetailUiState(
task = taskResult.data,
isTaskDeleted = isDeleted
)
}.stateIn(
scope = viewModelScope,
started = SharingStarted.WhileSubscribed(5_000),
initialValue = TaskDetailUiState()
)
fun deleteTask() = viewModelScope.launch {
tasksRepository.deleteTask(taskId)
_isTaskDeleted.update { true }
}
}
This interview continues for subscribers
Subscribe to Dove Letter for full access to exclusive interviews about Android and Kotlin development.
Become a Sponsor