mutableStateOf ist eine Funktion in Jetpack Compose, die einen Container für veränderlichen beobachtbaren Zustand erstellt. Wenn sich der Wert in diesem Container ändert, löst Compose automatisch die Neuzusammensetzung aller Komponenten aus, die diesen Zustand lesen. Ohne mutableStateOf könnte die UI nicht reaktiv auf Datenänderungen aktualisiert werden. Laut Google Android Developers, 2026 ist mutableStateOf der grundlegende Baustein für lokalen Zustand in Compose.
Wichtige Punkte
mutableStateOf ist eine Funktion aus dem Paket compose.runtime, die ein MutableState<T>-Objekt erstellt, das einen Wert speichert und Compose Runtime über Änderungen benachrichtigen kann. Signatur: fun <T> mutableStateOf(value: T, policy: SnapshotMutationPolicy<T> = structuralEquality()): MutableState<T>. Der Parameter policy bestimmt, wann eine Änderung als signifikant gilt — bei struktureller Gleichheit, referenzieller Gleichheit oder nie.
MutableState ist ein Interface mit einer einzigen Eigenschaft value: einem Getter zum Lesen und einem Setter zum Schreiben. Wenn der Setter aufgerufen wird, zeichnet Compose Runtime die Änderung in einem Snapshot auf und markiert alle Composable-Funktionen, die diese State-Variable lesen, als neuzusammensetzungsbedürftig. Dieser Prozess erfolgt synchron innerhalb eines einzigen Snapshot-Zyklus, was Zwischenzustände bei kaskadierenden Änderungen eliminiert.
Parameter policy — das zweite Argument von mutableStateOf, definiert das Vergleichsverhalten. structuralEquality() prüft equals() — dies ist das Standardverhalten. referentialEquality() prüft === (referenzielle Gleichheit). neverEqual() betrachtet jede Zuweisung als Änderung. Die Wahl der policy beeinflusst, ob bei Zuweisung desselben Werts eine Neuzusammensetzung ausgelöst wird.
Der einfachste Weg, beobachtbaren Zustand zu deklarieren, ist die Verwendung von mutableStateOf mit remember. Ohne remember würde jede Neuzusammensetzung einen neuen State erstellen, und alle vorherigen Änderungen gingen verloren. remember stellt sicher, dass derselbe MutableState eine Reihe von Neuzusammensetzungen überlebt, solange die Composable-Funktion in der Komposition verbleibt.
@Composable
fun Counter() {
// Ohne Delegation: Lesen/Schreiben über .value
val count = remember { mutableStateOf(0) }
Button(onClick = { count.value++ }) {
Text("Count: ${count.value}")
}
}
@Composable
fun CounterDelegated() {
// Mit Delegation: var + by = Property Delegation
var count by remember { mutableStateOf(0) }
Button(onClick = { count++ }) {
Text("Count: $count")
}
}
Der Unterschied zwischen den beiden Ansätzen ist syntaktisch. Property Delegation (by) verwendet eine Kotlin-Konvention: Der Compiler generiert getValue()- und setValue()-Aufrufe zum Lesen und Schreiben. Dies entspricht dem direkten Zugriff auf count.value, sieht aber aus wie die Arbeit mit einer normalen Variablen. Beide Ansätze sind funktional identisch: Compose verfolgt das Lesen im Getter und das Schreiben im Setter unabhängig von der Notation.
| Form | Code | Lesen | Schreiben |
|---|---|---|---|
| Ohne Delegation | val count = mutableStateOf(0) | count.value | count.value = n |
| Mit Delegation | var count by mutableStateOf(0) | count | count = n |
Der Mechanismus der delegierten Eigenschaften in Kotlin ist keine Compose-Funktion, sondern eine eingebaute Sprachfähigkeit. Jede Klasse kann die Operatoren getValue(thisRef, property) und setValue(thisRef, property, value) implementieren, wonach ihre Instanz mit dem Schlüsselwort by verwendet werden kann. MutableState funktioniert genau so: getValue gibt den aktuellen Wert zurück und setValue weist einen neuen zu.
Ein wichtiger Unterschied: val vs var. mutableStateOf kann sowohl val als auch var zugewiesen werden. Bei val (val count = mutableStateOf(0)) ist das MutableState-Objekt selbst unveränderlich, aber seine Eigenschaft value kann geändert werden. Bei var (var count by mutableStateOf(0)) erzeugt die Delegation die Illusion, mit einem primitiven Typ zu arbeiten, aber der Setter ruft tatsächlich setValue auf MutableState auf. Die Wahl zwischen val und var ist die Wahl zwischen explizitem und implizitem Zugriff auf .value.
State Delegation ist syntaktischer Zucker, der den Code vereinfacht, aber die Mechanik nicht ändert. Der Kotlin-Compiler übersetzt var x by mutableStateOf(0) in Getter/Setter, die mutableStateOf.getValue() und mutableStateOf.setValue() aufrufen. Im generierten Bytecode gibt es keinen Unterschied zwischen val und var mit by — beide arbeiten über denselben MutableState-Container.
// Benutzerdefinierter Delegat für 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 ist ein Mechanismus von Compose Runtime, der die Lesekonsistenz von State bei parallelen Änderungen gewährleistet. Wenn eine Composable-Funktion mutableStateOf liest, zeichnet der Snapshot den aktuellen Wert auf. Wenn während der Komposition eine andere Änderung in denselben State schreibt, sieht der Snapshot die Schreiboperation, erlaubt aber kein Lesen inkonsistenter Daten — das Lesen gibt immer den zu Beginn des Snapshots gültigen Wert zurück.
Wenn der Setter mutableStateOf.value = newValue aufgerufen wird, löst Compose Runtime nicht sofort eine Neuzusammensetzung aus. Stattdessen wird die Änderung im aktuellen Snapshot registriert. Wenn der Snapshot angewendet wird (an der Framegrenze), durchläuft Compose die Liste der geänderten States und markiert die lesenden Komponenten als Invalid. Erst im nächsten Frame beginnt die Neuzusammensetzung. Dies garantiert, dass die UI bei kaskadierenden Änderungen nicht dutzende Male neu gezeichnet wird.
Globale und lokale Snapshots: Standardmäßig arbeitet mutableStateOf im globalen Snapshot, der automatisch angewendet wird. Sie können einen lokalen Snapshot über Snapshot.takeSnapshot() für isoliertes Lesen ohne Nebenwirkungen erstellen. Dies wird innerhalb von Modifier verwendet, wenn Sie State lesen müssen, ohne sich für Änderungen zu abonnieren. Dieser Ansatz optimiert die Leistung und verhindert unerwartete Neuzusammensetzungen.
Betrachten wir ein reales Szenario — ein Login-Formular mit drei Feldern: E-Mail, Passwort und Ladezustand. Alle drei Felder verwenden mutableStateOf, jedoch mit unterschiedlichen policy und unterschiedlichen Verschachtelungsebenen. email verwendet Delegation, password verwendet direkten Zugriff.
data class LoginState(
val email: String = "",
val password: String = "",
val isLoading: Boolean = false,
val error: String? = null
)
@Composable
fun LoginForm(onLogin: (String, String) -> Unit) {
// Einzelner State für Formular, 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("Passwort") },
visualTransformation = PasswordVisualTransformation()
)
Button(
onClick = { onLogin(formState.email, formState.password) },
enabled = isValid
) {
Text("Anmelden")
}
}
}
In diesem Beispiel wird mutableStateOf mit einer benutzerdefinierten Datenklasse LoginState und policy referentialEquality verwendet. Dies bedeutet, dass die Neuzusammensetzung nur ausgelöst wird, wenn eine neue LoginState-Instanz über copy() zugewiesen wird. isValid wird basierend auf formState berechnet und nur neu berechnet, wenn es sich ändert. Dieser Ansatz bietet eine klare Kontrolle über Neuzusammensetzungen: Jedes Formularfeld ändert sich nur durch Erstellen einer neuen Kopie.
Häufig gestellte Fragen
mutableStateOf ist ein Compose-spezifischer Container, der innerhalb von Snapshots arbeitet. StateFlow stammt aus kotlinx.coroutines.flow und ist nicht an Compose gebunden. mutableStateOf löst automatisch eine Neuzusammensetzung aus, während StateFlow collectAsState() erfordert. Für den UI-Zustand innerhalb von Composable ist mutableStateOf vorzuziehen.
Ja, mutableStateOf kann außerhalb von Composable-Funktionen aufgerufen werden, wird dann aber nicht verfolgt. Für die Reaktivität in der UI muss State innerhalb von Composable gelesen werden. Viele ViewModels verwenden MutableStateField (einen Wrapper um mutableStateOf), um Zustand über StateFlow an die UI zu übergeben.
Das Snapshot-System garantiert Konsistenz: Jede Neuzusammensetzung sieht einen konsistenten Zustand zu Beginn des Snapshots. Änderungen aus verschiedenen Threads werden atomar an der Framegrenze angewendet, was Wettlaufsituationen beim Lesen innerhalb einer einzelnen Komposition eliminiert.
Weisen Sie einen neuen Wert zu: count.value = 0 (oder count = 0 bei Delegation). Wenn Sie den State vollständig neu erstellen müssen, verwenden Sie remember mit einem Schlüssel: remember(key) { mutableStateOf(initial) } — wenn sich der Schlüssel ändert, wird der State neu erstellt.
Composer verwendet Snapshots, die Änderungen gruppieren: Selbst bei hunderten Zuweisungen in einem einzigen Frame wird die Neuzusammensetzung nur einmal ausgeführt. Für sehr häufige Aktualisierungen (Animationen) verwenden Sie Animatable oder animate*AsState — sie sind für frame-basierte Aktualisierungen optimiert.
Zusammenfassung
Wir entwickeln eine mobile Applikation schlüsselfertig
IT Sectr entwickelt seit 2017 iOS- und Android-Apps für Startups und Unternehmen. Wir beraten Sie und schlagen die beste Lösung vor.
Lesen Sie auch