Build: Counter Screen
A simple screen that shows a number and updates it reactively using StateFlow when the user taps a button.
Target UI
------------------- | Counter: 0 | | | | [Increment] | -------------------
------------------- | Counter: 0 | | | | [Increment] | -------------------
import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow class CounterViewModel : ViewModel() { // TODO: Create a private MutableStateFlow for the counter starting at 0 // TODO: Expose a public StateFlow for the counter // TODO: Create a function to increment the counter } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) setContent { CounterScreen() } } } @Composable fun CounterScreen(viewModel: CounterViewModel = viewModel()) { // TODO: Collect the counter StateFlow as state Scaffold( topBar = { TopAppBar(title = { Text("Counter Screen") }) }, content = { paddingValues -> // TODO: Display the counter value and an Increment button } ) }
import androidx.activity.ComponentActivity import androidx.activity.compose.setContent import androidx.compose.foundation.layout.* import androidx.compose.material3.* import androidx.compose.runtime.* import androidx.lifecycle.ViewModel import androidx.lifecycle.viewmodel.compose.viewModel import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.asStateFlow class CounterViewModel : ViewModel() { private val _counter = MutableStateFlow(0) val counter: StateFlow<Int> = _counter.asStateFlow() fun increment() { _counter.value += 1 } } class MainActivity : ComponentActivity() { override fun onCreate(savedInstanceState: android.os.Bundle?) { super.onCreate(savedInstanceState) setContent { CounterScreen() } } } @Composable fun CounterScreen(viewModel: CounterViewModel = viewModel()) { val count by viewModel.counter.collectAsState() Scaffold( topBar = { TopAppBar(title = { Text("Counter Screen") }) }, content = { paddingValues -> Column( modifier = androidx.compose.ui.Modifier .fillMaxSize() .padding(paddingValues) .padding(16.dp), verticalArrangement = Arrangement.Center, horizontalAlignment = androidx.compose.ui.Alignment.CenterHorizontally ) { Text(text = "Counter: $count", style = MaterialTheme.typography.headlineMedium) Spacer(modifier = androidx.compose.ui.Modifier.height(24.dp)) Button(onClick = { viewModel.increment() }) { Text("Increment") } } } ) }
We use MutableStateFlow inside the CounterViewModel to hold the current count. This is private to prevent external modification. We expose a read-only StateFlow so the UI can observe changes.
The increment() function updates the counter value by increasing it by 1.
In the CounterScreen composable, we collect the StateFlow as Compose state using collectAsState(). This makes the UI automatically update when the counter changes.
The UI shows the current count in a Text and has a button labeled "Increment" that calls increment() when clicked.
------------------- | Counter: 5 | | | | [Increment] | -------------------
StateFlow in Android Kotlin development?MutableStateFlow(initialValue).val state = MutableStateFlow(0) state.value = 5 println(state.value)
value property can be updated directly, changing the current state.val state = MutableStateFlow(0)
fun updateState(newValue: Int) {
state.emit(newValue)
}emit function is a suspend function and requires a coroutine context to be called.updateState is not marked suspend and does not launch a coroutine, so calling emit here causes an error.asStateFlow() to expose a read-only StateFlow to observers outside the ViewModel.Pass the test and this pattern moves unknown