Bird
Raised Fist0
Android Kotlinmobile~20 mins

StateFlow for reactive state in Android Kotlin - Mini App: Build & Ship

Choose your learning style10 modes available
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]   |
-------------------
Use StateFlow in a ViewModel to hold the counter state.
Display the current counter value on the screen.
Add a button labeled 'Increment' that increases the counter by 1 when tapped.
The UI should update automatically when the counter changes.
Starter Code
Android Kotlin
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
        }
    )
}
Task 1
Task 2
Task 3
Task 4
Task 5
Task 6
Solution
Android Kotlin
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.

Final Result
Completed Screen
-------------------
|   Counter: 5    |
|                 |
|   [Increment]   |
-------------------
When the user taps the 'Increment' button, the counter number increases by 1 immediately.
The displayed number updates reactively without needing to refresh the screen.
Stretch Goal
Add a 'Reset' button that sets the counter back to zero.
💡 Hint
Add a reset() function in the ViewModel that sets _counter.value = 0, then add a second Button in the UI that calls reset() when clicked.

Practice

(1/5)
1. What is the main purpose of StateFlow in Android Kotlin development?
easy
A. To hold and emit state updates reactively to observers
B. To perform background network requests
C. To store user preferences permanently
D. To manage database transactions

Solution

  1. Step 1: Understand StateFlow's role

    StateFlow is designed to hold a state value and emit updates whenever the state changes.
  2. Step 2: Compare with other options

    Options A, B, and D describe unrelated tasks like storage, networking, or database management, which are not StateFlow's purpose.
  3. Final Answer:

    To hold and emit state updates reactively to observers -> Option A
  4. Quick Check:

    StateFlow = reactive state holder [OK]
Hint: StateFlow always relates to reactive state updates [OK]
Common Mistakes:
  • Confusing StateFlow with networking or storage APIs
  • Thinking StateFlow stores data permanently
  • Assuming StateFlow manages database directly
2. Which of the following is the correct way to declare a mutable StateFlow holding an integer value starting at 0?
easy
A. val state = StateFlow<Int>(0)
B. val state = MutableStateFlow(0)
C. val state = mutableStateFlowOf(0)
D. val state = StateFlow.mutable(0)

Solution

  1. Step 1: Recall MutableStateFlow syntax

    The correct way to create a mutable StateFlow is using MutableStateFlow(initialValue).
  2. Step 2: Check each option

    val state = StateFlow<Int>(0) uses StateFlow constructor which is not allowed. val state = mutableStateFlowOf(0) uses a non-existent function. val state = StateFlow.mutable(0) uses invalid syntax.
  3. Final Answer:

    val state = MutableStateFlow(0) -> Option B
  4. Quick Check:

    MutableStateFlow = mutable state holder [OK]
Hint: MutableStateFlow constructor takes initial value directly [OK]
Common Mistakes:
  • Trying to instantiate StateFlow directly
  • Using wrong function names like mutableStateFlowOf
  • Incorrect syntax for mutable StateFlow creation
3. Given the following code snippet, what will be printed to the console?
val state = MutableStateFlow(0)

state.value = 5
println(state.value)
medium
A. Compilation error
B. 0
C. null
D. 5

Solution

  1. Step 1: Understand MutableStateFlow value assignment

    MutableStateFlow's value property can be updated directly, changing the current state.
  2. Step 2: Trace the code execution

    Initially, state.value is 0. Then it is set to 5. Printing state.value outputs 5.
  3. Final Answer:

    5 -> Option D
  4. Quick Check:

    state.value after assignment = 5 [OK]
Hint: MutableStateFlow.value updates immediately and can be read directly [OK]
Common Mistakes:
  • Assuming initial value remains unchanged
  • Thinking value is nullable and prints null
  • Expecting compilation error due to direct assignment
4. Identify the error in the following Kotlin code using StateFlow:
val state = MutableStateFlow(0)

fun updateState(newValue: Int) {
  state.emit(newValue)
}
medium
A. Cannot call emit on MutableStateFlow
B. MutableStateFlow does not hold an initial value
C. emit is a suspend function and must be called from a coroutine
D. StateFlow cannot be updated after creation

Solution

  1. Step 1: Check usage of emit on MutableStateFlow

    The emit function is a suspend function and requires a coroutine context to be called.
  2. Step 2: Analyze the code context

    The function updateState is not marked suspend and does not launch a coroutine, so calling emit here causes an error.
  3. Final Answer:

    emit is a suspend function and must be called from a coroutine -> Option C
  4. Quick Check:

    emit requires coroutine context [OK]
Hint: emit is suspend; call only inside coroutine or suspend function [OK]
Common Mistakes:
  • Calling emit outside coroutine without suspend
  • Confusing emit with direct value assignment
  • Assuming MutableStateFlow has no initial value
5. You want to expose a read-only StateFlow from a ViewModel while keeping the ability to update the state internally. Which of the following is the best practice to achieve this?
hard
A. Keep a private MutableStateFlow and expose a public StateFlow using asStateFlow()
B. Expose the MutableStateFlow as a public property and update it internally
C. Declare a public MutableStateFlow and update it directly from outside the ViewModel
D. Use a regular Flow instead of StateFlow for state updates

Solution

  1. Step 1: Understand encapsulation of state

    To prevent external modification, keep MutableStateFlow private inside ViewModel.
  2. Step 2: Expose read-only StateFlow

    Use asStateFlow() to expose a read-only StateFlow to observers outside the ViewModel.
  3. Step 3: Evaluate other options

    Declare a public MutableStateFlow and update it directly from outside the ViewModel breaks encapsulation. Expose the MutableStateFlow as a public property and update it internally exposes mutable state publicly. Use a regular Flow instead of StateFlow for state updates changes the reactive type unnecessarily.
  4. Final Answer:

    Keep a private MutableStateFlow and expose a public StateFlow using asStateFlow() -> Option A
  5. Quick Check:

    Private MutableStateFlow + public asStateFlow() = best practice [OK]
Hint: Use private MutableStateFlow + public asStateFlow() for safe exposure [OK]
Common Mistakes:
  • Making MutableStateFlow public and mutable externally
  • Not using asStateFlow() to expose read-only state
  • Replacing StateFlow with Flow unnecessarily
Recommended

Ready to prove it?

Pass the test and this pattern moves unknown strong while earning +1 Grip toward your certificate.

10 questions~5 mineasy hard