Der Status in einer App ist ein beliebiger Wert, der sich im Laufe der Zeit ändern kann. Dies ist eine sehr weit gefasste Definition, die alles von einer Room-Datenbank bis hin zu einer Variablen in einer Klasse umfasst.
Alle Android-Apps zeigen dem Nutzer den Status an. Einige Beispiele für den Status in Android-Apps:
- Eine Snackbar, die angezeigt wird, wenn keine Netzwerkverbindung hergestellt werden kann.
- Ein Blogpost und zugehörige Kommentare.
- Wellenanimationen auf Schaltflächen, die abgespielt werden, wenn ein Nutzer darauf klickt.
- Sticker, die Nutzer über ein Bild zeichnen können.
Mit Jetpack Compose können Sie genau festlegen, wo und wie Sie den Status in einer Android-App speichern und verwenden. In diesem Leitfaden geht es um die Verbindung zwischen Status und Composables sowie um die APIs, die Jetpack Compose für die Arbeit mit dem Status bietet.
Zustand und Zusammensetzung
Compose ist deklarativ. Daher kann es nur aktualisiert werden, wenn dieselbe zusammensetzbare Funktion mit neuen Argumenten aufgerufen wird. Diese Argumente sind Darstellungen des UI-Status. Jedes Mal, wenn ein Status aktualisiert wird, findet eine Neuzusammensetzung statt. Daher werden Elemente wie TextField nicht automatisch aktualisiert, wie es bei imperativen XML-basierten Ansichten der Fall ist. Einer komponierbaren Funktion muss der neue Zustand explizit mitgeteilt werden, damit sie entsprechend aktualisiert wird.
@Composable private fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField( value = "", onValueChange = { }, label = { Text("Name") } ) } }
Wenn Sie dies ausführen und versuchen, Text einzugeben, passiert nichts. Das liegt daran, dass sich die TextField nicht selbst aktualisiert, sondern nur, wenn sich der Parameter value ändert. Das liegt daran, wie die Komposition und Neukomposition in Compose funktionieren.
Erstmalige Erstellung: Erstellung einer Komposition durch erstmaliges Ausführen von Compose-Funktionen.
Neuzusammensetzung: Erneutes Ausführen von Compose-Funktionen, um die Zusammensetzung zu aktualisieren, wenn sich Daten ändern.
Weitere Informationen zur ersten Komposition und zur Neukomposition finden Sie unter Thinking in Compose.
Zustand in composables
Komponierbare Funktionen können die remember API verwenden, um ein Objekt im Speicher zu speichern. Ein von remember berechneter Wert wird während der ersten Komposition in der Komposition gespeichert und der gespeicherte Wert wird während der Neukomposition zurückgegeben. remember kann zum Speichern von änderbaren und unveränderlichen Objekten verwendet werden.
mutableStateOf erstellt ein beobachtbares MutableState<T>, das ein beobachtbarer Typ ist, der in die Compose-Laufzeit integriert ist.
interface MutableState<T> : State<T> {
override var value: T
}
Alle Änderungen an value führen dazu, dass alle zusammensetzbaren Funktionen, die value lesen, neu zusammengesetzt werden.
Es gibt drei Möglichkeiten, ein MutableState-Objekt in einer zusammensetzbaren Funktion zu deklarieren:
val mutableState = remember { mutableStateOf(default) }var value by remember { mutableStateOf(default) }val (value, setValue) = remember { mutableStateOf(default) }
Diese Deklarationen sind äquivalent und werden als syntaktischer Sugar für verschiedene Verwendungen des Status bereitgestellt. Wählen Sie die Option aus, die den am einfachsten zu lesenden Code in der von Ihnen geschriebenen zusammensetzbaren Funktion erzeugt.
Für die Delegatsyntax by sind die folgenden Importe erforderlich:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Du kannst den gespeicherten Wert als Parameter für andere Composables oder sogar als Logik in Anweisungen verwenden, um zu ändern, welche Composables angezeigt werden. Wenn Sie beispielsweise die Begrüßung nicht anzeigen möchten, wenn der Name leer ist, verwenden Sie den Status in einer if-Anweisung:
@Composable fun HelloContent() { Column(modifier = Modifier.padding(16.dp)) { var name by remember { mutableStateOf("") } if (name.isNotEmpty()) { Text( text = "Hello, $name!", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } ) } }
Mit remember kannst du den Status über Neuzusammensetzungen hinweg beibehalten, aber nicht über Konfigurationsänderungen hinweg. Dazu müssen Sie rememberSaveable verwenden. rememberSaveable speichert automatisch alle Werte, die in einem Bundle gespeichert werden können. Für andere Werte können Sie ein benutzerdefiniertes Saver-Objekt übergeben.
Andere unterstützte Statustypen
Compose erfordert nicht, dass Sie MutableState<T> verwenden, um den Status zu speichern. Es werden auch andere beobachtbare Typen unterstützt. Bevor Sie einen anderen beobachtbaren Typ in Compose lesen, müssen Sie ihn in einen State<T> konvertieren, damit zusammensetzbare Funktionen bei einer Statusänderung automatisch neu zusammengesetzt werden können.
Compose enthält Funktionen zum Erstellen von State<T> aus gängigen beobachtbaren Typen, die in Android-Apps verwendet werden. Bevor Sie diese Integrationen verwenden, fügen Sie die entsprechenden Artefakte so hinzu:
Flow:collectAsStateWithLifecycle()collectAsStateWithLifecycle()erfasst Werte aus einemFlowauf lebenszyklusbewusste Weise, sodass Ihre App Ressourcen sparen kann. Er stellt den neuesten ausgegebenen Wert aus dem Compose-Statedar. Diese API ist die empfohlene Methode zum Erfassen von Abläufen in Android-Apps.Die folgende Abhängigkeit ist in der Datei
build.gradleerforderlich (sie sollte 2.6.0-beta01 oder höher sein):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.11.0")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.11.0"
}
-
collectAsStateähneltcollectAsStateWithLifecycle, da damit auch Werte aus einemFlowerfasst und in Compose-Stateumgewandelt werden.Verwenden Sie
collectAsStatefür plattformunabhängigen Code anstelle voncollectAsStateWithLifecycle, das nur für Android gilt.Für
collectAsStatesind keine zusätzlichen Abhängigkeiten erforderlich, da es incompose-runtimeverfügbar ist. -
observeAsState()beginnt mit der Beobachtung diesesLiveDataund stellt seine Werte mitStatedar.Die folgende Abhängigkeit ist in der Datei
build.gradleerforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.12.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.12.1"
}
-
subscribeAsState()sind Erweiterungsfunktionen, die die reaktiven Streams von RxJava2 (z.B.Single,Observable,Completable) in Compose-Stateumwandeln.Die folgende Abhängigkeit ist in der Datei
build.gradleerforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.12.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.12.1"
}
-
subscribeAsState()sind Erweiterungsfunktionen, die die reaktiven Streams von RxJava3 (z.B.Single,Observable,Completable) in Compose-Stateumwandeln.Die folgende Abhängigkeit ist in der Datei
build.gradleerforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.12.1")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.12.1"
}
Zustandsorientiert oder zustandslos
Eine zusammensetzbare Funktion, die remember zum Speichern eines Objekts verwendet, erstellt einen internen Status, wodurch die zusammensetzbare Funktion zustandsorientiert wird. HelloContent ist ein Beispiel für eine zustandsorientierte zusammensetzbare Funktion, da sie ihren name-Zustand intern speichert und ändert. Dies kann in Situationen nützlich sein, in denen ein Aufrufer den Status nicht steuern muss und ihn verwenden kann, ohne den Status selbst verwalten zu müssen. Kompositionen mit internem Status sind jedoch in der Regel weniger wiederverwendbar und schwieriger zu testen.
Eine zustandslose zusammensetzbare Funktion ist eine zusammensetzbare Funktion, die keinen Zustand enthält. Eine gängige Methode, um Zustandslosigkeit zu erreichen, ist das Status-Hoisting.
Wenn Sie wiederverwendbare komponierbare Funktionen entwickeln, möchten Sie oft sowohl eine zustandsorientierte als auch eine zustandslose Version derselben komponierbaren Funktion bereitstellen. Die zustandsorientierte Version ist praktisch für Aufrufer, die sich nicht um den Zustand kümmern, und die zustandslose Version ist für Aufrufer erforderlich, die den Zustand steuern oder hochstufen müssen.
Status-Hoisting
Status-Hoisting in Compose ist ein Muster, bei dem der Status zum Aufrufer einer zusammensetzbaren Funktion verschoben wird, um die zusammensetzbare Funktion zustandslos zu machen. Das allgemeine Muster für das Status-Hoisting in Jetpack Compose besteht darin, die Statusvariable durch zwei Parameter zu ersetzen:
value: T: Der aktuelle Wert, der angezeigt werden soll.onValueChange: (T) -> Unit: Ein Ereignis, das die Änderung des Werts anfordert, wobeiTder vorgeschlagene neue Wert ist.
Sie sind jedoch nicht auf onValueChange beschränkt. Wenn für das Compose-Element spezifischere Ereignisse geeignet sind, sollten Sie diese mit Lambdas definieren.
Ein auf diese Weise hochgezogener Status hat einige wichtige Eigenschaften:
- Zentrale Informationsquelle: Indem wir den Status verschieben, anstatt ihn zu duplizieren, sorgen wir dafür, dass es nur eine zentrale Informationsquelle gibt. So lassen sich Fehler vermeiden.
- Gekapselt:Nur zustandsorientierte Composables können ihren Zustand ändern. Es ist komplett intern.
- Teilbar:Der hochgezogene Status kann für mehrere Composables freigegeben werden. Wenn Sie
namein einer anderen zusammensetzbaren Funktion lesen möchten, können Sie das mit Hoisting tun. - Abfangbar: Aufrufer der zustandslosen Composables können Ereignisse ignorieren oder ändern, bevor der Zustand geändert wird.
- Entkoppelt: Der Status für die zustandslosen Composables kann an einem beliebigen Ort gespeichert werden. Beispielsweise ist es jetzt möglich,
namein einViewModelzu verschieben.
In diesem Beispiel extrahieren Sie name und onValueChange aus HelloContent und verschieben sie im Baum nach oben zu einer HelloScreen-komponierbaren Funktion, die HelloContent aufruft.
@Composable fun HelloScreen() { var name by rememberSaveable { mutableStateOf("") } HelloContent(name = name, onNameChange = { name = it }) } @Composable fun HelloContent(name: String, onNameChange: (String) -> Unit) { Column(modifier = Modifier.padding(16.dp)) { Text( text = "Hello, $name", modifier = Modifier.padding(bottom = 8.dp), style = MaterialTheme.typography.bodyMedium ) OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") }) } }
Wenn Sie den Status aus HelloContent herausnehmen, ist es einfacher, die zusammensetzbare Funktion zu verstehen, sie in verschiedenen Situationen wiederzuverwenden und zu testen. HelloContent ist von der Speicherung des Status entkoppelt. Entkopplung bedeutet, dass Sie die Implementierung von HelloContent nicht ändern müssen, wenn Sie HelloScreen ändern oder ersetzen.
Das Muster, bei dem der Status nach unten und die Ereignisse nach oben gehen, wird als unidirektionaler Datenfluss bezeichnet. In diesem Fall wird der Status von HelloScreen auf HelloContent herabgestuft und Ereignisse werden von HelloContent auf HelloScreen hochgestuft. Wenn Sie dem unidirektionalen Datenfluss folgen, können Sie die Composables, die den Status in der Benutzeroberfläche anzeigen, von den Teilen Ihrer App entkoppeln, die den Status speichern und ändern.
- Der Status sollte mindestens auf das niedrigste gemeinsame übergeordnete Element aller Composables hochgezogen werden, die den Status verwenden (lesen).
- Der Status sollte mindestens auf die höchste Ebene, auf der er geändert werden kann (Schreiben), hochgezogen werden.
- Wenn sich zwei Zustände als Reaktion auf dieselben Ereignisse ändern, sollten sie zusammen hochgezogen werden.
Sie können den Status höher als von diesen Regeln gefordert hochstufen. Wenn Sie den Status jedoch zu niedrig hochstufen, ist es schwierig oder unmöglich, dem unidirektionalen Datenfluss zu folgen.
Weitere Informationen finden Sie auf der Seite Wo der Status hochgezogen werden sollte.
Status in Compose wiederherstellen
Die rememberSaveable API verhält sich ähnlich wie remember, da sie den Status über Neuzusammensetzungen hinweg beibehält und auch über die Neuerstellung von Aktivitäten oder Prozessen hinweg, indem sie den Mechanismus für den gespeicherten Instanzstatus verwendet. Das passiert beispielsweise, wenn der Bildschirm gedreht wird.
Möglichkeiten zum Speichern des Zustands
Alle Datentypen, die dem Bundle hinzugefügt werden, werden automatisch gespeichert. Wenn Sie etwas speichern möchten, das nicht zur Bundle hinzugefügt werden kann, gibt es mehrere Möglichkeiten.
Parcelize
Die einfachste Lösung besteht darin, dem Objekt die Annotation @Parcelize hinzuzufügen. Das Objekt wird parcelable und kann gebündelt werden. Mit diesem Code wird beispielsweise ein parcelable City-Datentyp erstellt und im Status gespeichert.
@Parcelize data class City(val name: String, val country: String) : Parcelable @Composable fun CityScreen() { var selectedCity = rememberSaveable { mutableStateOf(City("Madrid", "Spain")) } }
MapSaver
Wenn @Parcelize aus irgendeinem Grund nicht geeignet ist, können Sie mit mapSaver eine eigene Regel definieren, um ein Objekt in eine Reihe von Werten zu konvertieren, die das System im Bundle speichern kann.
data class City(val name: String, val country: String) val CitySaver = run { val nameKey = "Name" val countryKey = "Country" mapSaver( save = { mapOf(nameKey to it.name, countryKey to it.country) }, restore = { City(it[nameKey] as String, it[countryKey] as String) } ) } @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
ListSaver
Wenn Sie die Schlüssel für die Zuordnung nicht definieren möchten, können Sie auch listSaver und die zugehörigen Indexe als Schlüssel verwenden:
data class City(val name: String, val country: String) val CitySaver = listSaver<City, Any>( save = { listOf(it.name, it.country) }, restore = { City(it[0] as String, it[1] as String) } ) @Composable fun CityScreen() { var selectedCity = rememberSaveable(stateSaver = CitySaver) { mutableStateOf(City("Madrid", "Spain")) } }
State Holder in Compose
Einfaches Status-Hoisting kann in den zusammensetzbaren Funktionen selbst verwaltet werden. Wenn jedoch die Menge des zu verfolgenden Zustands zunimmt oder die Logik, die in zusammensetzbaren Funktionen ausgeführt werden soll, entsteht, ist es eine gute Praxis, die Logik- und Zustandsverantwortlichkeiten an andere Klassen zu delegieren: State Holder.
In anderen Materialien werden State Holder auch als hochgestufte Zustandsobjekte bezeichnet.
Weitere Informationen finden Sie in der Dokumentation zum Status-Hoisting in Compose oder im Architekturleitfaden auf der Seite State Holder und UI-Status.
Berechnungen für „remember“ neu auslösen, wenn sich Schlüssel ändern
Die remember API wird häufig zusammen mit MutableState verwendet:
var name by remember { mutableStateOf("") }
Wenn Sie hier die Funktion remember verwenden, bleibt der Wert MutableState bei Neuzusammensetzungen erhalten.
Im Allgemeinen verwendet remember einen calculation-Lambda-Parameter. Wenn remember zum ersten Mal ausgeführt wird, wird die Lambda-Funktion calculation aufgerufen und das Ergebnis gespeichert. Während der Neuzusammensetzung gibt remember den zuletzt gespeicherten Wert zurück.
Neben dem Caching des Status können Sie remember auch verwenden, um ein beliebiges Objekt oder Ergebnis eines Vorgangs in der Composition zu speichern, dessen Initialisierung oder Berechnung aufwendig ist. Sie möchten diese Berechnung wahrscheinlich nicht bei jeder Neuzusammensetzung wiederholen.
Ein Beispiel ist das Erstellen dieses ShaderBrush-Objekts, was ein ressourcenintensiver Vorgang ist:
val brush = remember { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) }
remember speichert den Wert, bis er die Komposition verlässt. Es gibt jedoch eine Möglichkeit, den im Cache gespeicherten Wert zu entwerten. Die remember API akzeptiert auch einen key- oder keys-Parameter. Wenn sich einer dieser Schlüssel ändert, wird beim nächsten Neuzusammensetzen der Funktion der Cache von remember ungültig gemacht und der Lambda-Block für die Berechnung noch einmal ausgeführt. Mit diesem Mechanismus können Sie die Lebensdauer eines Objekts in der Composition steuern. Die Berechnung bleibt gültig, bis sich die Eingaben ändern, und nicht, bis der gespeicherte Wert die Composition verlässt.
Die folgenden Beispiele zeigen, wie dieser Mechanismus funktioniert.
In diesem Snippet wird ein ShaderBrush erstellt und als Hintergrundfarbe einer Box-Komposition verwendet. remember speichert die ShaderBrush-Instanz, da sie wie bereits erwähnt teuer zu erstellen ist. remember verwendet avatarRes als Parameter key1, also das ausgewählte Hintergrundbild. Wenn sich avatarRes ändert, wird der Pinsel mit dem neuen Bild neu zusammengesetzt und wieder auf Box angewendet. Das kann passieren, wenn der Nutzer ein anderes Bild als Hintergrund auswählt.
@Composable private fun BackgroundBanner( @DrawableRes avatarRes: Int, modifier: Modifier = Modifier, res: Resources = LocalContext.current.resources ) { val brush = remember(key1 = avatarRes) { ShaderBrush( BitmapShader( ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(), Shader.TileMode.REPEAT, Shader.TileMode.REPEAT ) ) } Box( modifier = modifier.background(brush) ) { /* ... */ } }
Im nächsten Snippet wird der Status in eine einfache State Holder-Klasse
MyAppState hochgezogen. Sie stellt eine rememberMyAppState-Funktion bereit, um eine Instanz der Klasse mit remember zu initialisieren. Das Bereitstellen solcher Funktionen zum Erstellen einer Instanz, die Rekompositionen übersteht, ist ein gängiges Muster in Compose. Die Funktion rememberMyAppState empfängt windowSizeClass, das als key-Parameter für remember dient. Wenn sich dieser Parameter ändert, muss die App die einfache State Holder-Klasse mit dem neuesten Wert neu erstellen. Das kann beispielsweise passieren, wenn der Nutzer das Gerät dreht.
@Composable private fun rememberMyAppState( windowSizeClass: WindowSizeClass ): MyAppState { return remember(windowSizeClass) { MyAppState(windowSizeClass) } } @Stable class MyAppState( private val windowSizeClass: WindowSizeClass ) { /* ... */ }
Compose verwendet die equals-Implementierung der Klasse, um zu entscheiden, ob sich ein Schlüssel geändert hat, und den gespeicherten Wert zu entwerten.
Zustand mit Schlüsseln über die Neuzusammensetzung hinaus speichern
Die rememberSaveable API ist ein Wrapper um remember, der Daten in einem Bundle speichern kann. Mit dieser API bleibt der Status nicht nur bei der Neuzusammensetzung, sondern auch bei der Neuerstellung von Aktivitäten und der vom System initiierten Prozessbeendigung erhalten.
rememberSaveable empfängt input-Parameter für denselben Zweck, für den remember keys empfängt. Der Cache wird ungültig, wenn sich eine der Eingaben ändert. Wenn die Funktion das nächste Mal neu zusammengesetzt wird, führt rememberSaveable den Lambda-Block für die Berechnung noch einmal aus.
Im folgenden Beispiel speichert rememberSaveable den Wert userTypedQuery, bis sich typedQuery ändert:
var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) { mutableStateOf( TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length)) ) }
Weitere Informationen
Weitere Informationen zu State und Jetpack Compose findest du in den folgenden zusätzlichen Ressourcen.
Beispiele
Codelabs
Videos
Blogs
Empfehlungen für Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Compose-UI entwerfen
- UI-Status in Compose speichern
- Nebeneffekte in Compose