mutableStateOf is een functie in Jetpack Compose die een container van veranderlijke waarneembare toestand creëert. Wanneer de waarde in deze container verandert, start Compose automatisch de recompositie van alle componenten die deze toestand lezen. Zonder mutableStateOf zou de UI niet reactief kunnen updaten bij gegevenswijzigingen. Volgens Google Android Developers, 2026 is mutableStateOf de primaire bouwsteen voor lokale state in Compose.
Belangrijkste punten
mutableStateOf is een functie uit het compose.runtime-pakket die een MutableState<T>-object creëert dat een waarde opslaat en Compose Runtime op de hoogte kan stellen van wijzigingen. Handtekening: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. De parameter policy bepaalt wanneer een wijziging als significant wordt beschouwd — bij structurele gelijkheid, bij referentiële gelijkheid of nooit.
MutableState is een interface met een enkele eigenschap value: getter voor lezen en setter voor schrijven. Bij het aanroepen van de setter registreert Compose Runtime de wijziging in een snapshot en markeert alle Composable-functies die deze State-variabele lezen als recompositie vereisend. Dit proces vindt synchroon plaats binnen één snapshot-cyclus, waardoor tussentoestanden bij cascade-wijzigingen worden geëlimineerd.
Parameter policy — het tweede argument van mutableStateOf, dat het gedrag bij vergelijking bepaalt. structuralEquality() controleert equals() — dit is het standaardgedrag. referentialEquality() controleert === (referentiële gelijkheid). neverEqual() beschouwt elke toewijzing als een wijziging. De keuze van policy beïnvloedt of recompositie wordt gestart bij het toewijzen van dezelfde waarde.
De eenvoudigste manier om waarneembare toestand te declareren is het gebruik van mutableStateOf met remember. Zonder remember zou bij elke recompositie een nieuwe State worden aangemaakt en zouden alle voorgaande wijzigingen verloren gaan. remember garandeert dat dezelfde MutableState een reeks recomposities overleeft zolang de Composable-functie in de compositie blijft.
@Composable
fun Counter() {
// Zonder delegatie: lezen/schrijven via .value
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// Met delegatie: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
Het verschil tussen de twee varianten is syntactisch. Property Delegation (by) gebruikt de Kotlin-conventie: de compiler genereert getValue()- en setValue()-aanroepen voor lezen en schrijven. Dit is gelijkwaardig aan directe toegang tot count.value, maar ziet eruit als werken met een gewone variabele. Beide varianten zijn functioneel identiek: Compose volgt lezen in de getter en schrijven in de setter, ongeacht de schrijfwijze.
| Vorm | Code | Lezen | Schrijven |
|---|---|---|---|
| Zonder delegatie | val count = mutableStateOf(0) | count.value | count.value = n |
| Met delegatie | var count by mutableStateOf(0) | count | count = n |
Het mechanisme van gedelegeerde eigenschappen in Kotlin is geen kenmerk van Compose, maar een ingebouwde mogelijkheid van de taal. Elke klasse kan de operatoren getValue(thisRef, property) en setValue(thisRef, property, value) implementeren, waarna het exemplaar kan worden gebruikt met het sleutelwoord by. MutableState werkt precies zo: getValue retourneert de huidige waarde en setValue kent een nieuwe toe.
Belangrijk verschil: val en var. mutableStateOf kan zowel aan val als aan var worden toegewezen. In het geval van val (val count = mutableStateOf(0)) is het MutableState-object zelf onveranderlijk, maar de eigenschap value kan worden gewijzigd. In het geval van var (var count by mutableStateOf(0)) creëert delegatie de illusie van werken met een primitief, maar in werkelijkheid roept de setter setValue aan op MutableState. De keuze tussen val en var is een keuze tussen expliciete en impliciete toegang tot .value.
State Delegation is syntactische suiker die de code vereenvoudigt, maar de mechanica niet verandert. De Kotlin-compiler transpileert var x by mutableStateOf(0) naar getter/setter die mutableStateOf.getValue() en mutableStateOf.setValue() aanroepen. In de gegenereerde bytecode is er geen verschil tussen val en var met by — beide werken via dezelfde MutableState-container.
// Aangepaste delegatie voor Compose State
class ValidatedState<T>(initialValue: T) {
private val state = mutableStateOf(initialValue)
operator fun getValue(thisRef: Any?, property: KProperty<*>) = state.value
operator fun setValue(thisRef: Any?, property: KProperty<*>, value: T) {
if (value != state.value) {
state.value = value
}
}
}
@Composable
fun Test() {
var text by remember { ValidatedState("") }
}
Snapshot is een mechanisme van Compose Runtime dat de consistentie van het lezen van State bij parallelle wijzigingen waarborgt. Wanneer een Composable-functie mutableStateOf leest, registreert de snapshot de huidige waarde. Als tijdens de compositie een andere wijziging in dezelfde State schrijft, ziet de snapshot de schrijving, maar staat het lezen van inconsistente gegevens niet toe — lezen retourneert altijd de waarde die actueel is op het moment van het starten van de snapshot.
Bij het aanroepen van de setter mutableStateOf.value = newValue start Compose Runtime niet onmiddellijk de recompositie. In plaats daarvan wordt de wijziging geregistreerd in de huidige snapshot. Wanneer de snapshot wordt toegepast (op de framegrens), doorloopt Compose de lijst van gewijzigde State en markeert de lezende componenten als Invalid. Pas in het volgende frame wordt de recompositie gestart. Dit garandeert dat de UI niet tientallen keren wordt hertekend bij cascade-wijzigingen.
Globale en lokale snapshots: standaard werkt mutableStateOf in een globale snapshot die automatisch wordt toegepast. Er kan een lokale snapshot worden gemaakt via Snapshot.takeSnapshot() voor geïsoleerd lezen zonder bijwerkingen. Dit wordt gebruikt binnen Modifier, waar State moet worden gelezen, maar niet moet worden geabonneerd op wijzigingen. Deze aanpak optimaliseert de prestaties en voorkomt onverwachte recomposities.
Laten we een realistisch scenario bekijken — een inlogformulier met drie velden: e-mail, wachtwoord en laadstatus. Alle drie de velden gebruiken mutableStateOf, maar met verschillende policy en verschillende nesting. e-mail gebruikt delegatie, wachtwoord — directe toegang.
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// Enkele State voor formulier, policy = referentialEquality
var formState by remember {
mutableStateOf(LoginState(), SnapshotMutationPolicy.referentialEquality())
}
val isValid = remember(formState) {
formState.email.contains("@") && formState.password.length() >= 6
}
Column(modifier = Modifier.padding(16.dp)) {
OutlinedTextField(
value = formState.email,
onValueChange = { formState = formState.copy(email = it) },
label = { Text("E-mail") }
)
OutlinedTextField(
value = formState.password,
onValueChange = { formState = formState.copy(password = it) },
label = { Text("Wachtwoord") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("Inloggen")
}
}
}
In dit voorbeeld wordt mutableStateOf gebruikt met een aangepaste data-klasse LoginState en policy referentialEquality. Dit betekent dat recompositie alleen wordt gestart bij het toewijzen van een nieuw LoginState-exemplaar via copy(). isValid wordt berekend op basis van formState en alleen herberekend wanneer deze verandert. Deze aanpak geeft duidelijke controle over recomposities: elk formulierveld verandert alleen door het maken van een nieuwe kopie.
Veelgestelde vragen
mutableStateOf is een Compose-specifieke container die binnen snapshots werkt. StateFlow komt van kotlinx.coroutines.flow en is niet gebonden aan Compose. mutableStateOf start automatisch recompositie, StateFlow vereist collectAsState(). Voor UI-state binnen Composable heeft mutableStateOf de voorkeur.
Ja, mutableStateOf kan buiten een Composable-functie worden aangeroepen, maar wordt dan niet gevolgd. Voor reactiviteit in de UI moet State binnen Composable worden gelezen. Veel ViewModels gebruiken MutableStateField (een wrapper over mutableStateOf) om state naar de UI te sturen via StateFlow.
Snapshot systeem garandeert consistentie: elke recompositie ziet de consistente toestand op het moment van starten van de snapshot. Wijzigingen vanuit verschillende threads worden atomair toegepast op de framegrens, wat race conditions bij het lezen binnen één compositie elimineert.
Wijs een nieuwe waarde toe: count.value = 0 (of count = 0 bij delegatie). Als volledige hercreatie van State nodig is — gebruik remember met een sleutel: remember(key) { mutableStateOf(initial) } — bij wijziging van de sleutel wordt State opnieuw aangemaakt.
Composer gebruikt snapshots die wijzigingen groeperen: zelfs met honderd toewijzingen in één frame wordt recompositie slechts één keer uitgevoerd. Voor zeer frequente updates (animaties) gebruikt u Animatable of animate*AsState — deze zijn geoptimaliseerd voor frame-updates.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook