Der Status in einer App ist jeder Wert, der sich im Laufe der Zeit ändern kann. Diese Definition ist sehr breit gefasst und umfasst alles von einer Room-Datenbank bis hin zu einer Variablen in einer Klasse.
Alle Android-Apps zeigen dem Nutzer einen 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 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 zusammensetzbaren Funktionen sowie um die APIs, die Jetpack Compose bietet, um einfacher mit dem Status zu arbeiten.
Status und Zusammensetzung
Compose ist deklarativ. Daher kann es nur aktualisiert werden, indem dieselbe zusammensetzbare Funktion mit neuen Argumenten aufgerufen wird. Diese Argumente stellen den UI-Status dar. 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 zusammensetzbaren Funktion muss der neue Status 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 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 Zusammensetzung und Neuzusammensetzung in Compose funktionieren.
Weitere Informationen zur erstmaligen Zusammensetzung und Neuzusammensetzung finden Sie unter In Compose denken.
Status in zusammensetzbaren Funktionen
Zusammensetzbare Funktionen können die
remember
API verwenden, um ein Objekt im Arbeitsspeicher zu speichern. Ein von remember berechneter Wert wird bei der erstmaligen Zusammensetzung in der Zusammensetzung gespeichert und bei der Neuzusammensetzung 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
}
Bei Änderungen an value wird die Neuzusammensetzung aller zusammensetzbaren Funktionen geplant, die value lesen.
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 gleichwertig und dienen als Syntaxzucker für verschiedene Verwendungen des Status. Sie sollten die Deklaration auswählen, die den am einfachsten zu lesenden Code in der zusammensetzbaren Funktion erzeugt, die Sie schreiben.
Für die by-Delegatensyntax 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 zusammensetzbare Funktionen oder sogar als Logik in Anweisungen verwenden, um zu ändern, welche zusammensetzbaren Funktionen 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 Neuzusammensetzungen beibehalten. Bei Konfigurationsänderungen wird der Status jedoch nicht 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 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 zusammensetzbare Funktionen
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 wie unten beschrieben 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 muss 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 überStatedar.Die folgende Abhängigkeit ist in der
build.gradleDatei erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-livedata:1.11.0")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-livedata:1.11.0"
}
-
subscribeAsState()sind Erweiterungsfunktionen, die reaktive Streams von RxJava2 (z.B.Single,Observable,Completable) in Compose-Stateumwandeln.Die folgende Abhängigkeit ist in der
build.gradleDatei erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava2:1.11.0")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava2:1.11.0"
}
-
subscribeAsState()sind Erweiterungsfunktionen, die reaktive Streams von RxJava3 (z.B.Single,Observable,Completable) in Compose-Stateumwandeln.Die folgende Abhängigkeit ist in der
build.gradleDatei erforderlich:
Kotlin
dependencies {
...
implementation("androidx.compose.runtime:runtime-rxjava3:1.11.0")
}
Groovy
dependencies {
...
implementation "androidx.compose.runtime:runtime-rxjava3:1.11.0"
}
Zustandsorientiert versus zustandslos
Eine zusammensetzbare Funktion, die remember zum Speichern eines Objekts verwendet, erstellt einen internen Status und macht die zusammensetzbare Funktion zustandsorientiert. HelloContent ist ein Beispiel für eine zustandsorientierte zusammensetzbare Funktion, da sie ihren 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. Zusammensetzbare Funktionen mit internem Status sind jedoch in der Regel weniger wiederverwendbar und schwieriger zu testen.
Eine zustandslose zusammensetzbare Funktion ist eine zusammensetzbare Funktion, die keinen Status enthält. Eine einfache Möglichkeit, Zustandslosigkeit zu erreichen, ist das Status Hoisting.
Wenn Sie wiederverwendbare zusammensetzbare Funktionen entwickeln, möchten Sie oft sowohl eine zustandsorientierte als auch eine zustandslose Version derselben zusammensetzbaren Funktion 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 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 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 die zusammensetzbare Funktion geeignet sind, sollten Sie sie mit Lambdas definieren.
Status, der auf diese Weise gehoistet wird, hat einige wichtige Eigenschaften:
- Eine einzige Datenquelle:Wenn wir den Status verschieben, anstatt ihn zu duplizieren, stellen wir sicher, dass es nur eine einzige Datenquelle gibt. So lassen sich Fehler vermeiden.
- Gekapselt:Nur zustandsorientierte zusammensetzbare Funktionen können ihren Status ändern. Er ist vollständig intern.
- Freigabefähig:Der gehoistete Status kann für mehrere zusammensetzbare Funktionen freigegeben werden. Wenn Sie
namein einer anderen zusammensetzbaren Funktion lesen möchten, können Sie das mit Hoisting tun. - Abfangbar:Aufrufer der zustandslosen zusammensetzbaren Funktionen können Ereignisse ignorieren oder ändern, bevor sie den Status ändern.
- Entkoppelt:Der Status für die zustandslosen zusammensetzbaren Funktionen 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 einer HelloScreen-zusammensetzbaren 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") }) } }
Durch das Hoisting des Status aus HelloContent ist es einfacher, die zusammensetzbare Funktion zu verstehen, sie in verschiedenen Situationen wiederzuverwenden und zu testen. HelloContent ist von der Speicherung des Status entkoppelt. Das 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 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 zusammensetzbare Funktionen, die den Status in der UI anzeigen, von den Teilen Ihrer App entkoppeln, die den Status speichern und ändern.
Weitere Informationen finden Sie auf der Seite Wohin sollte der Status gehoistet werden?
Status in Compose wiederherstellen
Die rememberSaveable-API verhält sich ähnlich wie remember, da sie den Status bei Neuzusammensetzungen 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")) } }
Status-Holder in Compose
Einfaches Status-Hoisting kann in den zusammensetzbaren Funktionen selbst verwaltet werden. Wenn jedoch die Menge des zu verfolgenden Status zunimmt oder die Logik in zusammensetzbaren Funktionen ausgeführt werden muss, ist es eine gute Idee, die Logik- und Statusverantwortlichkeiten an andere Klassen zu delegieren: Status-Holder.
Weitere Informationen finden Sie in der Dokumentation zum Status-Hoisting in Compose oder allgemeiner auf der Seite Status-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 von MutableState Neuzusammensetzungen ü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 Neuzusammensetzung 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 Zusammensetzung zu speichern, deren Initialisierung oder Berechnung aufwendig ist. Sie möchten diese Berechnung möglicherweise nicht bei jeder Neuzusammensetzung 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 Zusammensetzung verlässt. Es gibt jedoch eine Möglichkeit, den im Cache gespeicherten Wert zu entwerten. Die remember API verwendet auch einen key oder
keys Parameter. Wenn sich einer dieser Schlüssel ändert, entwertet
remember beim nächsten Mal, wenn die Funktion
neu zusammengesetzt wird, den Cache und führt den Lambda-Block für die Berechnung noch einmal aus. Mit diesem Mechanismus können Sie die Lebensdauer eines Objekts in der Zusammensetzung steuern. Die Berechnung bleibt gültig, bis sich die Eingaben ändern, und nicht bis der gespeicherte Wert die Zusammensetzung verlässt.
Die folgenden Beispiele zeigen, wie dieser Mechanismus funktioniert.
In diesem Snippet wird ein ShaderBrush erstellt und als Hintergrund
farbe einer Box zusammensetzbaren Funktion 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 die Box angewendet. Das kann passieren, wenn der Nutzer in der 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 Status-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 Neuzusammensetzungen ü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 Status-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 entwertet den gespeicherten Wert.
Status mit Schlüsseln über die Neuzusammensetzung 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 Neuzusammensetzungen, sondern auch der Neuerstellung von Aktivitäten und dem vom System initiierten Beenden von Prozessen überstehen.
rememberSaveable empfängt input Parameter für denselben Zweck, für den
remember keys empfängt. Der Cache wird entwertet, wenn sich eine der Eingaben ändert. Beim nächsten Mal, wenn die Funktion neu zusammengesetzt wird, führt rememberSaveable den Lambda-Block für die Berechnung 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 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