Der Status in einer App ist ein Wert, der sich im Laufe der Zeit ändern kann. Das ist eine sehr weit gefasste Definition und umfasst alles von einer Room-Datenbank bis hin zu einer Variablen in einer Klasse.
Alle Android-Apps zeigen den Status für den Nutzer an. Einige Beispiele für 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 auf ein Bild kleben können.
Mit Jetpack Compose können Sie genau angeben, wo und wie Sie den Status in einer Android-App speichern und verwenden. In diesem Leitfaden geht es um die Verbindung zwischen Status und Compose-Elementen und um die APIs, die Jetpack Compose bietet, um einfacher mit Status zu arbeiten.
Status und Zusammensetzung
Compose ist deklarativ und kann daher nur durch Aufrufen desselben Composeable mit neuen Argumenten aktualisiert werden. Diese Argumente sind Darstellungen des UI-Status. Jedes Mal, wenn ein Status aktualisiert wird, erfolgt eine Neuzusammensetzung. Daher werden Elemente wie TextField
nicht automatisch aktualisiert, wie es in imperativ XML-basierten Ansichten der Fall ist. Ein Composeable muss explizit über den neuen Status informiert werden, damit es 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 das Script ausführen und versuchen, Text einzugeben, passiert nichts. Das liegt daran, dass sich TextField
nicht selbst aktualisiert, sondern nur, wenn sich der value
-Parameter ändert. Das liegt daran, wie Komposition und Neukomposition in Compose funktionieren.
Weitere Informationen zur ersten Komposition und Neukomposition finden Sie unter Komposition als Denkweise.
Status in komponierbaren Funktionen
Zusammensetzbare Funktionen können die remember
API verwenden, um ein Objekt im Arbeitsspeicher zu speichern. Ein von remember
berechneter Wert wird bei der ersten Komposition in der Komposition gespeichert und bei der Neukomposition zurückgegeben.
remember
kann sowohl zum Speichern von veränderlichen als auch von unveränderlichen Objekten verwendet werden.
mutableStateOf
erstellt ein Observable MutableState<T>
, einen Observable-Typ, der in die Compose-Laufzeit eingebunden ist.
interface MutableState<T> : State<T> {
override var value: T
}
Ä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 einem Composeable 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 Zucker für verschiedene Verwendungen von „state“ bereitgestellt. Wählen Sie die Option aus, die im von Ihnen geschriebenen Composeable den am besten lesbaren Code erzeugt.
Für die by
-Delegierungssyntax sind die folgenden Importe erforderlich:
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
Sie können den gespeicherten Wert als Parameter für andere Composeables oder sogar als Logik in Anweisungen verwenden, um zu ändern, welche Composeables angezeigt werden. Wenn Sie beispielsweise nicht möchten, dass die Begrüßung angezeigt wird, 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
können Sie den Status bei Neuzusammensetzungen beibehalten, er wird jedoch nicht bei Konfigurationsänderungen beibehalten. 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 Speicherobjekt übergeben.
Andere unterstützte Statustypen
Für Compose ist es nicht erforderlich, MutableState<T>
zum Speichern des Status zu verwenden. Es werden auch andere Observable-Typen unterstützt. Bevor Sie einen anderen Observable-Typ in Compose lesen, müssen Sie ihn in einen State<T>
konvertieren, damit Composables automatisch neu zusammengesetzt werden können, wenn sich der Status ändert.
Erstellen Sie Schiffe mit Funktionen, um State<T>
aus gängigen Observable-Typen zu erstellen, die in Android-Apps verwendet werden. Bevor Sie diese Integrationen verwenden, fügen Sie die entsprechenden Artefakte hinzu, wie unten beschrieben:
Flow
:collectAsStateWithLifecycle()
collectAsStateWithLifecycle()
ruft Werte aus einemFlow
nutzungsabhängig ab, sodass Ihre App App-Ressourcen einsparen kann. Er entspricht dem zuletzt gesendeten Wert von „Compose“State
. Diese API ist die empfohlene Methode zum Erfassen von Abläufen in Android-Apps.In der Datei
build.gradle
ist die folgende Abhängigkeit erforderlich (Version 2.6.0-beta01 oder höher):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.8.7")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.8.7"
}
-
collectAsState
ähneltcollectAsStateWithLifecycle
, da auch hier Werte aus einerFlow
erfasst und in „Komponieren“State
umgewandelt werden.Verwenden Sie
collectAsState
für plattformunabhängigen Code anstelle voncollectAsStateWithLifecycle
, das nur für Android gilt.Für
collectAsState
sind keine zusätzlichen Abhängigkeiten erforderlich, da es incompose-runtime
verfügbar ist. -
observeAsState()
beginnt, dieseLiveData
zu beobachten und stellt ihre Werte überState
dar.In der Datei
build.gradle
ist die folgende Abhängigkeit erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.7.5")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.7.5"
}
-
subscribeAsState()
sind Erweiterungsfunktionen, die die reaktiven Streams von RxJava2 (z.B.Single
,Observable
,Completable
) in ComposeState
umwandeln.In der Datei
build.gradle
ist die folgende Abhängigkeit erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.7.5")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.7.5"
}
-
subscribeAsState()
sind Erweiterungsfunktionen, die die reaktiven Streams von RxJava3 (z.B.Single
,Observable
,Completable
) in ComposeState
umwandeln.In der Datei
build.gradle
ist die folgende Abhängigkeit erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.7.5")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.7.5"
}
Zustandsorientiert und zustandslos
Ein Composeable, das ein Objekt mit remember
speichert, erstellt einen internen Status und ist somit zustandsabhängig. HelloContent
ist ein Beispiel für ein zustandsorientiertes Kompositum, da es seinen name
-Status intern speichert und ändert. Das kann in Situationen nützlich sein, in denen ein Aufrufer den Status nicht steuern muss und ihn verwenden kann, ohne ihn selbst verwalten zu müssen. Allerdings sind Composeables mit internem Status in der Regel weniger wiederverwendbar und schwieriger zu testen.
Ein zustandsloser Composeable ist ein Composeable, das keinen Status hat. Eine einfache Möglichkeit, einen zustandslosen Dienst zu implementieren, ist das Staus-Hoisting.
Wenn Sie wiederverwendbare Composeables entwickeln, möchten Sie oft sowohl eine zustandsorientierte als auch eine zustandslose Version desselben Composeables freigeben. Die zustandsorientierte Version ist für Aufrufer praktisch, die sich nicht um den Status kümmern müssen. Die zustandslose Version ist für Aufrufer erforderlich, die den Status steuern oder anheben müssen.
Status-Hoisting
Das Zustands-Hoisting in Compose ist ein Muster, bei dem der Zustand an den Aufrufer eines Compose-Objekts verschoben wird, um es zustandslos zu machen. Beim allgemeinen Muster für das Heben des Status in Jetpack Compose wird die Statusvariable durch zwei Parameter ersetzt:
value: T
:Der aktuelle Wert, der angezeigt werden sollonValueChange: (T) -> Unit
:Ereignis, bei dem die Änderung des Werts angefordert wird.T
ist der vorgeschlagene neue Wert.
Sie sind jedoch nicht auf onValueChange
beschränkt. Wenn für die Composeable-Funktion spezifischere Ereignisse geeignet sind, sollten Sie sie mit Lambdas definieren.
So ausgelagerter Status hat einige wichtige Eigenschaften:
- Single Source of Truth: Indem wir den Status verschieben, anstatt ihn zu duplizieren, sorgen wir dafür, dass es nur eine einzige „Source of Truth“ gibt. So lassen sich Fehler vermeiden.
- Eingekapselt:Nur zustandsorientierte Composeables können ihren Status ändern. Sie ist vollständig intern.
- Teilbar:Der gehostete Status kann für mehrere Composeables freigegeben werden. Wenn Sie
name
in einem anderen Composeable lesen möchten, ist das mithilfe von Hoisting möglich. - Abfangbar:Aufrufer der zustandslosen Composeables können Ereignisse ignorieren oder ändern, bevor der Status geändert wird.
- Entkoppelt:Der Status der zustandslosen Composeables kann überall gespeichert werden. So ist es beispielsweise jetzt möglich,
name
in eineViewModel
zu verschieben.
Im Beispiel extrahieren Sie name
und onValueChange
aus HelloContent
und verschieben sie im Baum nach oben zu einem HelloScreen
-Komposit, das 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 der Status aus HelloContent
ausgelagert wird, lässt sich die Komponente leichter analysieren, in verschiedenen Situationen wiederverwenden und testen. HelloContent
ist von der Speicherung des Status entkoppelt. Wenn Sie HelloScreen
ändern oder ersetzen, müssen Sie die Implementierung von HelloContent
nicht ändern.
Das Muster, bei dem der Status sinkt und die Ereignisse steigen, wird als einseitiger Datenfluss bezeichnet. In diesem Fall sinkt der Status von HelloScreen
auf HelloContent
und die Anzahl der Ereignisse steigt von HelloContent
auf HelloScreen
. Wenn Sie einem unidirektionalen Datenfluss folgen, können Sie Komponenten, die den Status in der Benutzeroberfläche anzeigen, von den Teilen Ihrer App entkoppeln, die den Status speichern und ändern.
Weitere Informationen finden Sie auf der Seite Wo Status hochgehängt werden sollte.
Status in der Zeichenansicht wiederherstellen
Die rememberSaveable
API verhält sich ähnlich wie remember
, da der Status bei Neuzusammensetzungen und auch bei der Neuerstellung von Aktivitäten oder Prozessen mithilfe des Mechanismus für den gespeicherten Instanzstatus beibehalten wird. Das passiert beispielsweise, wenn das Display gedreht wird.
Möglichkeiten zum Speichern des Zustands
Alle Datentypen, die der Bundle
hinzugefügt werden, werden automatisch gespeichert. Wenn Sie etwas speichern möchten, das nicht zur Bundle
hinzugefügt werden kann, haben Sie mehrere Möglichkeiten.
Parcelize
Die einfachste Lösung besteht darin, dem Objekt die Annotation @Parcelize
hinzuzufügen. Das Objekt kann dann in Pakete aufgeteilt und in Sets angeboten werden. Mit diesem Code wird beispielsweise ein teilbarer 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 für die Umwandlung eines Objekts in eine Reihe von Werten definieren, die das System in der 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
verwenden und die 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
Das einfache Zustands-Hoisting kann in den zusammensetzbaren Funktionen selbst verwaltet werden. Wenn jedoch die Menge an Status, die Sie im Blick behalten müssen, zunimmt oder die Logik in kompositionsfähigen Funktionen ausgeführt werden muss, sollten Sie die Verantwortung für Logik und Status an andere Klassen delegieren: Statushalter.
Weitere Informationen finden Sie in der Dokumentation zum Zustandsaufstieg in Compose oder allgemeiner im Architekturleitfaden auf der Seite Statushalter und UI-Status.
Berechnungen für „Denken Sie daran“ bei Schlüsseländerungen noch einmal auslösen
Die remember
API wird häufig zusammen mit MutableState
verwendet:
var name by remember { mutableStateOf("") }
Hier bleibt der MutableState
-Wert durch die Verwendung der Funktion remember
bei Umordnungen erhalten.
Im Allgemeinen wird für remember
ein calculation
-Lambda-Parameter verwendet. Wenn remember
zum ersten Mal ausgeführt wird, wird das Lambda calculation
aufgerufen und das Ergebnis gespeichert. Während der Neuzusammensetzung gibt remember
den zuletzt gespeicherten Wert zurück.
Neben dem Caching-Status können Sie mit remember
auch Objekte oder Ergebnisse eines Vorgangs in der Komposition speichern, deren Initialisierung oder Berechnung aufwendig ist. Sie müssen diese Berechnung nicht bei jeder Neuzusammensetzung wiederholen.
Ein Beispiel ist das Erstellen dieses ShaderBrush
-Objekts, was ein teurer 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 den Parameter key
oder keys
. Wenn sich einer dieser Schlüssel ändert, remember
erhebt den Cache auf und führt den Lambda-Block noch einmal aus, wenn die Funktion das nächste Mal neu erstellt wird. Mit diesem Mechanismus können Sie die Lebensdauer eines Objekts in der Komposition steuern. Die Berechnung bleibt gültig, bis sich die Eingaben ändern, und nicht bis der gespeicherte Wert die Komposition verlässt.
Die folgenden Beispiele zeigen, wie dieser Mechanismus funktioniert.
In diesem Snippet wird ein ShaderBrush
erstellt und als Hintergrundfarbe eines Box
-Composeables verwendet. remember
speichert die Instanz ShaderBrush
, da das Erstellen sehr aufwendig ist, wie bereits erläutert. Für remember
wird avatarRes
als key1
-Parameter verwendet, also das ausgewählte Hintergrundbild. Wenn sich avatarRes
ändert, wird der Pinsel mit dem neuen Bild neu zusammengesetzt und auf die Box
angewendet. Das kann passieren, wenn der Nutzer in einer Auswahl 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 Statushalterklasse
MyAppState
verschoben. Sie stellt eine rememberMyAppState
-Funktion bereit, um eine Instanz der Klasse mit remember
zu initialisieren. Das Bereitstellen solcher Funktionen zum Erstellen einer Instanz, die bei Neuzusammensetzungen erhalten bleibt, 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 Statushalterklasse mit dem aktuellen Wert neu erstellen. Das kann z. B. 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 ungültig zu machen.
Status mit Schlüsseln speichern, die über die Neuzusammensetzung hinausgehen
Die rememberSaveable
API ist ein Wrapper für remember
, mit dem Daten in einer Bundle
gespeichert werden können. Mit dieser API bleibt der Status nicht nur nach der Neuzusammensetzung, sondern auch nach der Wiederherstellung von Aktivitäten und dem systeminitiierten Beenden des Prozesses erhalten.
rememberSaveable
empfängt input
-Parameter zu demselben Zweck wie remember
keys
. Der Cache wird ungültig, wenn sich eine der Eingaben ändert. Wenn die Funktion das nächste Mal neu erstellt wird, führt rememberSaveable
den Berechnungs-Lambda-Block noch einmal aus.
Im folgenden Beispiel speichert rememberSaveable
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 Status und Jetpack Compose finden Sie in den folgenden zusätzlichen Ressourcen.
Produktproben
Codelabs
Videos
Blogs
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Compose-Benutzeroberfläche entwerfen
- UI-Status in „Schreiben“ speichern
- Nebeneffekte in Compose