Der Status in einer App ist jeder Wert, der sich im Laufe der Zeit ändern kann. Das ist eine sehr weit gefasste Definition, die alles von einer Room-Datenbank bis zu einer Variablen in einer Klasse umfasst.
Alle Android-Apps zeigen den Status für den Nutzer an. Einige Beispiele für den Status in Android-Apps:
- Eine Snackbar, die angezeigt wird, wenn keine Netzwerkverbindung hergestellt werden kann.
- Ein Blogpost und die zugehörigen Kommentare.
- Ripple-Animationen auf Schaltflächen, die abgespielt werden, wenn ein Nutzer darauf klickt.
- Sticker, die ein Nutzer auf ein Bild zeichnen kann.
Mit Jetpack Compose können Sie explizit angeben, wo und wie Sie den Status in einer Android-App speichern und verwenden. In dieser Anleitung geht es um die Verbindung zwischen Status und Composables sowie um die APIs, die Jetpack Compose für die Arbeit mit dem Status bietet.
Status und Komposition
Compose ist deklarativ. Daher können Sie es nur aktualisieren, indem Sie dasselbe Composable mit neuen Argumenten aufrufen. Diese Argumente stellen den UI-Status dar. Jedes Mal, wenn ein Status aktualisiert wird, findet eine Neukomposition statt. Daher werden Elemente wie TextField nicht automatisch aktualisiert, wie es bei imperativen XML-basierten Ansichten der Fall ist. Einem Composable muss der neue Status explizit mitgeteilt 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 diesen Code ausführen und versuchen, Text einzugeben, passiert nichts. Das liegt daran, dass TextField sich nicht selbst aktualisiert, sondern nur, wenn sich der Parameter value ändert. Das liegt daran, wie Komposition und Neukomposition in Compose funktionieren.
Erste Komposition:Erstellung einer Komposition durch erstmaliges Ausführen von Composables.
Neukomposition:Composables werden noch einmal ausgeführt, um die Komposition zu aktualisieren, wenn sich Daten ändern.
Weitere Informationen zur ersten Komposition und Neukomposition finden Sie unter In Compose denken.
Status in Composables
Composable-Funktionen können die remember-API verwenden, um ein
Objekt im Arbeitsspeicher zu speichern. Ein von remember berechneter Wert wird während der ersten Komposition in der Komposition gespeichert und bei der Neukomposition zurückgegeben. remember kann verwendet werden, um sowohl veränderliche als auch unveränderliche Objekte zu speichern.
mutableStateOf erstellt ein beobachtbares
MutableState<T>,
einen beobachtbaren Typ, der in die Compose-Laufzeitumgebung integriert ist.
interface MutableState<T> : State<T> {
override var value: T
}
Alle Änderungen an value führen zu einer Neukomposition aller Composable-Funktionen, die value lesen.
Es gibt drei Möglichkeiten, ein MutableState-Objekt in einem Composable zu deklarieren:
val mutableState = remember { mutableStateOf(default) }var value by remember { mutableStateOf(default) }val (value, setValue) = remember { mutableStateOf(default) }
Diese Deklarationen sind gleichwertig und dienen als Syntaxzucker für verschiedene Verwendungen des Status. Sie sollten die Deklaration auswählen, die den am einfachsten zu lesenden Code im Composable erzeugt, das Sie schreiben.
Für die by-Delegat-Syntax 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 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 können Sie den Status bei Neukompositionen beibehalten, aber nicht bei Konfigurationsänderungen. 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.
Weitere unterstützte Statustypen
In Compose müssen Sie MutableState<T> nicht 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 State<T> konvertieren, damit Composables bei Statusänderungen
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 lebenszyklusbezogene Weise, sodass Ihre App Ressourcen sparen kann. Es stellt den letzten ausgegebenen Wert aus dem ComposeStatedar. Verwenden Sie diese API als empfohlene Methode zum Erfassen von Flows in Android-Apps.Die folgende Abhängigkeit ist in der
build.gradleDatei erforderlich (sie sollte 2.6.0-beta01 oder höher sein):
Kotlin
dependencies {
...
implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0")
}
Groovy
dependencies {
...
implementation "androidx.lifecycle:lifecycle-runtime-compose:2.10.0"
}
-
collectAsStateähneltcollectAsStateWithLifecycle, da es auch Werte aus einemFlowerfasst und in ComposeStateumwandelt.Verwenden Sie
collectAsStatefür plattformunabhängigen Code anstelle voncollectAsStateWithLifecycle, das nur für Android verfügbar ist.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
build.gradleDatei erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.11.4")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.11.4"
}
-
subscribeAsState()sind Erweiterungsfunktionen, die die reaktiven Streams von RxJava2 (z.B.Single,Observable,Completable) in ComposeStateumwandeln.Die folgende Abhängigkeit ist in der
build.gradleDatei erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.11.4")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.11.4"
}
-
subscribeAsState()sind Erweiterungsfunktionen, die die reaktiven Streams von RxJava3 (z.B.Single,Observable,Completable) in ComposeStateumwandeln.Die folgende Abhängigkeit ist in der
build.gradleDatei erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.11.4")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.11.4"
}
Zustandsorientiert im Vergleich zu zustandslos
Ein Composable, das remember zum Speichern eines Objekts verwendet, erstellt einen internen Status und macht das Composable zustandsorientiert. HelloContent ist ein Beispiel für ein zustandsorientiertes Composable, da es den Status name 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. Composable-Funktionen mit internem Status sind jedoch in der Regel weniger wiederverwendbar und schwieriger zu testen.
Ein zustandsloses Composable ist ein Composable, das keinen Status enthält. Eine gängige Methode, um Zustandslosigkeit zu erreichen, ist das Status-Hoisting.
Wenn Sie wiederverwendbare Composables entwickeln, möchten Sie oft sowohl eine zustandsorientierte als auch eine zustandslose Version desselben Composables bereitstellen. Die zustandsorientierte Version ist praktisch für Aufrufer, die sich nicht um den Status kümmern müssen, und die zustandslose Version ist für Aufrufer erforderlich, die den Status steuern oder hoisten müssen.
Status-Hoisting
Status-Hoisting in Compose ist ein Muster, bei dem der Status an den Aufrufer eines Composables verschoben wird, um das Composable 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 sollonValueChange: (T) -> Unit: ein Ereignis, das eine Änderung des Werts anfordert, wobeiTder vorgeschlagene neue Wert ist
Sie sind jedoch nicht auf onValueChange beschränkt. Wenn spezifischere Ereignisse für das Composable geeignet sind, sollten Sie sie mit Lambdas definieren.
Status, der auf diese Weise gehoistet wird, hat einige wichtige Eigenschaften:
- Eine einzige Datenquelle:Wenn Sie den Status verschieben, anstatt ihn zu duplizieren, gibt es nur eine einzige Datenquelle. So lassen sich Fehler vermeiden.
- Gekapselt:Nur zustandsorientierte Composables können ihren Status ändern. Er ist vollständig intern.
- Freigabefähig:Der gehoistete Status kann für mehrere Composables freigegeben werden. Wenn Sie
namein einem anderen Composable lesen möchten, können Sie das mit Hoisting tun. - Abfangbar:Aufrufer der zustandslosen Composables können Ereignisse ignorieren oder ändern, bevor sie den Status ändern.
- Entkoppelt:Der Status für die zustandslosen Composables kann überall gespeichert werden. So ist es jetzt beispielsweise möglich,
namein einViewModelzu verschieben.
Im Beispiel extrahieren Sie name und onValueChange aus HelloContent und verschieben sie im Baum nach oben zu einem HelloScreen-Composable, 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") }) } }
Durch das Hoisting des Status aus HelloContent ist es einfacher, das Composable zu verstehen, es in verschiedenen Situationen wiederzuverwenden und zu 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 nach unten und Ereignisse nach oben fließen, wird als unidirektionaler Datenfluss bezeichnet. In diesem Fall fließt der Status von HelloScreen nach HelloContent und Ereignisse von HelloContent nach HelloScreen. Wenn Sie dem unidirektionalen Datenfluss folgen, können Sie Composables, die den Status in der UI anzeigen, von den Teilen Ihrer App entkoppeln, die den Status speichern und ändern.
- Der Status sollte mindestens zum niedrigsten gemeinsamen übergeordneten Element aller Composables gehoistet werden, die den Status verwenden (lesen).
- Der Status sollte mindestens auf die höchste Ebene gehoistet werden, auf der er geändert werden kann (schreiben).
- Wenn sich zwei Status als Reaktion auf dieselben Ereignisse ändern , sollten sie zusammen gehoistet werden.
Sie können den Status höher hoisten, als diese Regeln erfordern. Wenn Sie den Status jedoch zu niedrig hoisten, ist es schwierig oder unmöglich, dem unidirektionalen Datenfluss zu folgen.
Weitere Informationen finden Sie auf der Seite Wo sollte der Status gehoistet werden?
Status in Compose wiederherstellen
Die rememberSaveable-API verhält sich ähnlich wie remember, da sie den Status bei Neukompositionen und auch bei der Neuerstellung von Aktivitäten oder Prozessen mithilfe des Mechanismus für den gespeicherten Instanzstatus beibehält. Das passiert beispielsweise, wenn der Bildschirm gedreht wird.
Möglichkeiten zum Speichern des Status
Alle Datentypen, die dem Bundle hinzugefügt werden, werden automatisch gespeichert. Wenn Sie etwas speichern möchten, das nicht zum Bundle hinzugefügt werden kann, haben Sie mehrere Möglichkeiten.
Parcelize
Die einfachste Lösung besteht darin, dem Objekt die @Parcelize
Annotation 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 zum Konvertieren eines Objekts in eine Reihe von Werten definieren, 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
Um die Schlüssel für die Map nicht definieren zu müssen, 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
Einfaches Status-Hoisting kann in den Composable-Funktionen selbst verwaltet werden. Wenn jedoch die Menge des zu verfolgenden Status zunimmt oder die Logik in Composable-Funktionen ausgeführt werden muss, ist es eine gute Idee, die Logik- und Statusverantwortlichkeiten an andere Klassen zu delegieren: State Holder.
In anderen Materialien werden State Holder auch als gehoistete Statusobjekte bezeichnet.
Weitere Informationen finden Sie in der Dokumentation zum Status-Hoisting in Compose oder allgemeiner auf der Seite State Holder und UI-Status im Architekturleitfaden.
Neuberechnung von „remember“ auslösen, wenn sich Schlüssel ändern
Die remember-API wird häufig zusammen mit MutableState verwendet:
var name by remember { mutableStateOf("") }
Hier sorgt die Verwendung der Funktion remember dafür, dass der Wert MutableState Neukompositionen übersteht.
Im Allgemeinen verwendet remember einen calculation-Lambda-Parameter. Wenn remember zum ersten Mal ausgeführt wird, ruft es das calculation-Lambda auf und speichert das Ergebnis. Bei der Neukomposition gibt remember den zuletzt gespeicherten Wert zurück.
Neben dem Caching des Status können Sie remember auch verwenden, um beliebige Objekte oder Ergebnisse eines Vorgangs in der Komposition zu speichern, deren Initialisierung oder Berechnung aufwendig ist. Sie möchten diese Berechnung möglicherweise nicht bei jeder Neukomposition wiederholen.
Ein Beispiel ist das Erstellen dieses ShaderBrush-Objekts, das ein aufwendiger
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 ungültig zu machen. Die remember API verwendet auch einen key oder
keys Parameter. Wenn sich einer dieser Schlüssel ändert, macht die Funktion beim nächsten Mal
neu zusammensetzt, remember den Cache ungültig und führt den Lambda-Block für die Berechnung erneut aus. 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 Hintergrund
farbe eines Box Composable verwendet. remember speichert die ShaderBrush-Instanz
, da die Neuerstellung aufwendig ist, wie bereits erklärt. remember verwendet avatarRes als key1-Parameter, das ausgewählte Hintergrundbild. Wenn sich avatarRes ändert, wird der Brush mit dem neuen Bild neu zusammengesetzt und wieder auf das 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 State Holder-Klasse
MyAppState gehoistet. Sie stellt eine rememberMyAppState-Funktion bereit, um eine
Instanz der Klasse mit remember zu initialisieren. Das Bereitstellen solcher Funktionen zum Erstellen einer Instanz, die Neukompositionen übersteht, ist ein gängiges Muster in Compose. Die
rememberMyAppState Funktion empfängt windowSizeClass, das als
der 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 macht den gespeicherten Wert ungültig.
Status mit Schlüsseln über die Neukomposition hinaus speichern
Die rememberSaveable-API ist ein Wrapper um remember, mit dem
Daten in einem Bundle gespeichert werden können. Mit dieser API kann der Status nicht nur Neukompositionen, sondern auch die Neuerstellung von Aktivitäten und den vom System initiierten Prozessabbruch überstehen.
rememberSaveable empfängt input Parameter für denselben Zweck, für den
remember keys empfängt. Der Cache wird ungültig gemacht, 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 erneut 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 zum Status und zu Jetpack Compose finden Sie in den folgenden zusätzlichen Ressourcen.
Beispiele
Codelabs
Videos
Blogs
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Compose-UI entwerfen
- UI-Status in Compose speichern
- Nebeneffekte in Compose