Member-only story
Bridging StateFlow and Jetpack Compose State: A Cleaner Architectural Approach

Managing UI state in Android apps often involves a delicate balance: Compose’s built-in state (mutableStateOf
) makes it incredibly easy to keep your UI updated, while Kotlin’s StateFlow
offers powerful, testable, and thread-safe reactive data streams. The challenge arises when you try to combine these approaches. While it’s simple to drop collectAsStateWithLifecycle()
calls into your composables, you end up repeating code and sprinkling collection logic throughout your UI layer.
In this article, we’ll show you a step-by-step method to leverage both Compose’s ergonomic property delegation and StateFlow’s reactive capabilities. We’ll start simple, then scale up to more complex scenarios, all while ensuring our composables remain focused on rendering and not on state management details.
Starting Simple: Compose’s Built-In State
Consider a basic ViewModel
controlling a splash screen:
data class MainState(
val showSplash: Boolean = false
)
class MainViewModel : ViewModel() {
var state by mutableStateOf(MainState())
private set
init {
viewModelScope.launch {
// Show splash for a couple of seconds
state = state.copy(showSplash = true)
delay(2000L)
state = state.copy(showSplash = false)
}
}
}
In your UI, this is straightforward:
@Composable
fun MainScreen(viewModel: MainViewModel = viewModel()) {
val state = viewModel.state
if (state.showSplash) {
SplashScreen()
} else {
MainContent()
}
}
Here, it feels natural: the composable just reads viewModel.state
. Compose takes care of recomposing when state
changes—no extra hoops to jump through.
Introducing StateFlow for More Complex Logic
As your app grows, you may need reactive streams that are testable, concurrent-safe, and easily combined. StateFlow excels here:
class MainViewModel : ViewModel() {
private val _state = MutableStateFlow(MainState())
val state: StateFlow<MainState> = _state
init {
viewModelScope.launch {…