Step 5 of 15
remember, mutableStateOf, state hoisting, derivedStateOf, unidirectional data flow
State และ recomposition — หัวใจของ Compose, state hoisting, unidirectional data flow
When the data behind your UI changes, Compose runs the composable again with the new values. This is called recomposition. You hold that changing data in state.
Use remember to keep a value across recompositions, and mutableStateOf to make it observable:
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.*
@Composable
fun Counter() {
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
remember — keeps the value when the function recomposesmutableStateOf(0) — wraps the value so Compose watches itby — property delegate so you read/write count directlycount changes, only this Button recomposes// Tap 3 times:
// Count: 0 → Count: 1 → Count: 2 → Count: 3
remember is lost when the Activity is destroyed (for example, on rotation). Use rememberSaveable to survive that:
var count by rememberSaveable { mutableStateOf(0) }
Now rotating the device keeps the count. rememberSaveable works with primitive types and Parcelable automatically.
A composable is easier to reuse when it does not own its state. Move state up to the caller and pass events down. This pattern is state hoisting.
Diagram: Unidirectional data flow — state flows down, events flow up.
Loading diagram...
Stateless composable (no internal state):
@Composable
fun NameField(name: String, onNameChange: (String) -> Unit) {
TextField(
value = name,
onValueChange = onNameChange,
label = { Text("Name") }
)
}
Stateful parent that hoists the state:
@Composable
fun NameForm() {
var name by rememberSaveable { mutableStateOf("") }
NameField(name = name, onNameChange = { name = it })
Text("You typed: $name")
}
The NameField is now reusable and testable — it just shows what it is given.
Use derivedStateOf when a value depends on other state and you want to recompute it only when needed:
@Composable
fun ShoppingCart() {
var itemCount by remember { mutableStateOf(0) }
val shipping by remember {
derivedStateOf {
if (itemCount == 0) 0.0
else if (itemCount < 5) 5.0
else 0.0
}
}
Text("Shipping: $$shipping")
}
shipping recomputes only when itemCount crosses a threshold, not on every change.
ViewModel).Build a tip calculator. A Slider sets the bill amount (0–100). A Text shows a 15% tip, computed with derivedStateOf. Use rememberSaveable so the bill survives rotation. Confirm the tip updates as you drag the slider.