MutableState ist eine Schnittstelle in Jetpack Compose, die einen Container für einen veränderlichen beobachtbaren Wert darstellt. Sie ist die Grundlage des reaktiven Systems von Compose: Jedes Mal, wenn sich der Wert von MutableState über den Setter ändert, benachrichtigt Compose Runtime alle lesenden Komponenten und löst eine Neuzusammensetzung aus. Laut Google Android Developers, 2026 ist das Verständnis von MutableState für die korrekte Arbeit mit Zustand in einer deklarativen UI zwingend erforderlich.
Wichtigste Punkte
MutableState ist eine Schnittstelle aus dem Paket androidx.compose.runtime, die eine einzelne Eigenschaft deklariert: override var value: T. Der Getter gibt den aktuellen Wert zurück, der Setter schreibt einen neuen und benachrichtigt Compose Runtime über die Änderung. Die Schnittstelle erbt von State<T>, bei dem value schreibgeschützt ist. Diese zweistufige Architektur ermöglicht eine Zugriffstrennung: Eine Komponente, die nur den Wert lesen muss, erhält State<T>, während die Eigentümer-Komponente MutableState<T> erhält.
Die Standardimplementierung von MutableState ist die interne Klasse SnapshotMutableStateImpl, die einen Snapshot-Mechanismus zur Verfolgung von Änderungen verwendet. Wenn der value-Setter aufgerufen wird, zeichnet der aktuelle Snapshot den Schreibvorgang auf und markiert alle registrierten ObservedScope als ungültig. Diese Bereiche (normalerweise Composable-Funktionen) werden im nächsten Frame neu zusammengesetzt. Der gesamte Prozess erfolgt synchron und ohne Sperren dank der Lock-free Snapshot-Architektur.
State vs MutableState: State ist eine schreibgeschützte Schnittstelle, die für öffentliche Komponenten-APIs verwendet wird. Wenn Sie einen Composable-Funktionsparameter als State<Int> deklarieren, garantieren Sie, dass die Komponente den Zustand lesen, aber nicht ändern kann. MutableState wird innerhalb der Eigentümer-Komponente verwendet. Diese Trennung ist eine der grundlegenden Compose-Praktiken, die unbefugte Änderungen verhindert.
Die Hierarchie der Zustandsschnittstellen in Compose hat mehrere Ebenen. An der Spitze steht State<T> mit einem schreibgeschützten value. Darunter befindet sich MutableState<T> mit einem lese-schreibbaren value. Weiter unten folgen spezialisierte primitive Versionen: MutableIntState, MutableFloatState, MutableLongState, MutableBooleanState und andere, die das Boxing von Primitiven vermeiden.
MutableDoubleState und MutableLongState sind weniger verbreitet, aber ebenfalls vorhanden. Sammlungs-Schnittstellen: MutableListState — zur Verfolgung von Änderungen innerhalb einer Liste, MutableStateMap — für Maps. Jede dieser Schnittstellen ist für ein bestimmtes Szenario optimiert und erweitert das grundlegende MutableState um zusätzliche Methoden zur Sammlungsmanipulation.
SnapshotStateList und SnapshotStateMap sind Implementierungen von veränderlichen Listen und Maps, die mit Snapshots kompatibel sind. Sie ermöglichen nicht nur die Verfolgung von Wertersetzungen, sondern auch von internen Änderungen: Hinzufügen eines Elements zu einer Liste, Entfernen, Ändern eines vorhandenen Elements. Für solche Strukturen erstellen mutableStateListOf() und mutableStateMapOf() die entsprechenden beobachtbaren Sammlungen.
| Schnittstelle | Zweck | Erstellungsmethode |
|---|---|---|
| State<T> | Schreibgeschützter Container | — |
| MutableState<T> | Lese-Schreib-Container | mutableStateOf() |
| MutableIntState | Primitiver Int ohne Boxing | mutableIntStateOf() |
| MutableFloatState | Primitiver Float ohne Boxing | mutableFloatStateOf() |
| SnapshotStateList | Beobachtbare Liste | mutableStateListOf() |
| SnapshotStateMap | Beobachtbare Map | mutableStateMapOf() |
SnapshotMutationPolicy ist eine Schnittstelle, die bestimmt, wann eine Änderung von MutableState als signifikant gilt. mutableStateOf akzeptiert policy als zweites Argument. Standardimplementierungen: structuralEquality() (equals), referentialEquality() (===), neverEqualPolicy() (betrachtet Änderungen immer als signifikant). Für benutzerdefinierte Logik kann eine eigene Richtlinie implementiert werden.
structuralEquality() — Standardverhalten. Compose vergleicht den neuen Wert mit dem alten über equals(). Wenn das Ergebnis true ist, wird die Neuzusammensetzung NICHT ausgelöst. Dies ist praktisch für Primitive und Data Classes, bei denen zwei Instanzen mit denselben Feldern als gleich betrachtet werden. Problem: Wenn eine Data Class eine List enthält, führt equals() einen tiefen Vergleich durch, der bei großen Listen kostspielig sein kann.
referentialEquality() — vergleicht Referenzen über ===. Die Neuzusammensetzung wird nur ausgelöst, wenn ein anderes Objekt zugewiesen wird, selbst wenn der Inhalt identisch ist. Dies ist optimal für unveränderliche Data Classes, bei denen jede neue Instanz eine Änderung garantiert. neverEqualPolicy() — betrachtet Änderungen immer als signifikant, ohne einen Vergleich durchzuführen. Nützlich, wenn der Setter selten aufgerufen wird und keine Zeit für equals aufgewendet werden muss.
// Policy comparison in practice
data class User(val name: String, val age: Int)
@Composable
fun UserProfile() {
// structuralEquality: recomposition ONLY if data changed
var user1 by remember {
mutableStateOf(User("Alice", 30))
}
// referentialEquality: recomposition on ANY assignment
var user2 by remember {
mutableStateOf(User("Bob", 25),
SnapshotMutationPolicy.referentialEquality())
}
// user1: copy() with same fields does NOT trigger recomposition
// user2: even user2.copy() == user2 triggers recomposition (new ref)
}
Primitive MutableIntState und ähnliche sind spezialisierte Schnittstellen, die Primitive ohne Boxing speichern. Ein normaler MutableState<Int> speichert Int als Integer, was bei jedem Schreibvorgang ein Objekt auf dem Heap erzeugt. MutableIntState speichert int (primitiv) und eliminiert damit vollständig den Boxing-Overhead. Dies ist besonders wichtig bei hochfrequenten Aktualisierungen — Zähler, Scroll-Positionen, Animationswerte.
mutableIntStateOf(), mutableFloatStateOf(), mutableLongStateOf() — Funktionen, die primitive MutableState erstellen. Die Schnittstellen heißen MutableIntState, MutableFloatState, MutableLongState. Sie erweitern MutableState<Int>, MutableState<Float> bzw. MutableState<Long> und fügen die Eigenschaft intValue für den schnellen Zugriff auf das Primitiv hinzu. Ihre interne Implementierung verwendet AtomicInteger für sperrfreies Lesen/Schreiben.
Verwendung: Zähler (Int), Scroll-Positionen (Float offset), Zeitstempel (Long). In den meisten alltäglichen Szenarien ist der Leistungsunterschied nicht wahrnehmbar, aber in LazyList mit Tausenden von Elementen und Übergangsanimationen bieten primitive State eine spürbare Verbesserung. Google empfiehlt die Verwendung von primitiven State für typische Szenarien anstelle des universellen mutableStateOf.
@Composable
fun ScrollCounter() {
// Bad: boxing on every update
var badCount by remember { mutableStateOf(0) }
// Good: no boxing, primitive storage
var goodCount by remember { mutableIntStateOf(0) }
// Usage is identical
Button(onClick = { goodCount++ }) {
Text("Count: $goodCount")
}
}
Betrachten wir eine TodoList-Komponente, bei der MutableState in zwei Formen verwendet wird: als separate Variablen für den Eingabezustand und als SnapshotStateList für eine dynamische Aufgabenliste. Beide verwenden Delegation zur Code-Verkürzung.
data class TodoItem(val id: Int, val text: String, val isDone: Boolean = false)
@Composable
fun TodoScreen() {
var inputText by remember { mutableStateOf("") }
val items = remember { mutableStateListOf() }
Column(modifier = Modifier.padding(16.dp)) {
Row {
TextField(
value = inputText,
onValueChange = { inputText = it }
)
Button(onClick = {
if (inputText.isNotBlank()) {
items.add(TodoItem(items.size, inputText))
inputText = ""
}
}) { Text("Add") }
}
LazyColumn {
items(items) { item ->
Row(modifier = Modifier.fillMaxWidth().clickable {
val idx = items.indexOf(item)
items[idx] = item.copy(isDone = !item.isDone)
}) {
Checkbox(checked = item.isDone, onCheckedChange = null)
Text(item.text)
}
}
}
}
}
mutableStateListOf erstellt eine SnapshotStateList — eine veränderliche Liste, die Änderungen einzelner Elemente verfolgt. Wenn items.add() und items[n] = newValue aufgerufen werden, sieht Compose die Mutation und setzt nur diejenigen LazyColumn-Elemente neu zusammen, die sich geändert haben. inputText ist ein normaler MutableState<String>. Die Kombination zweier MutableState-Typen (einzeln und Sammlung) ist ein typisches Muster für Bildschirme mit Formularen und Listen.
Häufig gestellte Fragen
MutableState ohne remember wird bei jeder Neuzusammensetzung neu erstellt. Jeder neue Aufruf von mutableStateOf erzeugt ein neues Objekt und der alte Wert geht verloren. Verwenden Sie immer remember, um den State zwischen Neuzusammensetzungen zu erhalten, es sei denn, der State wird außerhalb eines Composable erstellt (z. B. in einem ViewModel).
Lesen Sie .value einmal außerhalb eines Snapshots über snapshot { }. Dies deaktiviert jedoch die Reaktivität — Änderungen lösen keine Neuzusammensetzung mehr aus. Für einmaliges Lesen ohne Abonnement verwenden Sie currentValue() innerhalb eines Snapshots ohne Lesen.
mutableIntStateOf ist schneller, da es kein Boxing von int in Integer erfordert. Bei Tausenden von Aktualisierungen pro Sekunde (Animation, Scrollen) kann der Unterschied 30-50% der Zuordnungszeit betragen. Bei seltenen Aktualisierungen (Klicks, Texteingabe) ist der Unterschied vernachlässigbar.
Es ist möglich, aber nicht empfohlen. Übergeben Sie statt MutableState State (schreibgeschützt) + einen onValueChange-Lambda. Dies implementiert das State-Hoisting-Muster und macht die Komponente wiederverwendbar. Komponenten, die MutableState akzeptieren, verletzen den unidirektionalen Datenfluss.
Implementieren Sie die MutableState-Schnittstelle und stellen Sie override var value mit einem Getter und Setter bereit. Im Setter können Sie Validierung oder Protokollierung hinzufügen. Für die Abwärtskompatibilität mit Compose Runtime wickeln Sie Ihre benutzerdefinierte Implementierung in snapshotFlow ein oder verwenden Sie snapshotIncrement.
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