State und Jetpack Compose

Der Status einer App ist jeder Wert, der sich im Laufe der Zeit ändern kann. Dies ist eine sehr weit gefasste Definition und umfasst alles von einer Raumdatenbank bis zu einer Variablen in einer Klasse.

Der Status aller Android-Apps wird dem Nutzer angezeigt. Hier einige Beispiele für Status in Android-Apps:

  • Eine Snackbar, die anzeigt, wenn keine Netzwerkverbindung hergestellt werden kann.
  • Ein Blogpost und zugehörige Kommentare.
  • Wellenförmige Animationen auf Schaltflächen, die abgespielt werden, wenn ein Nutzer darauf klickt.
  • Sticker, die Nutzer auf einem Bild zeichnen können.

Mit Jetpack Compose können Sie deutlich machen, wo und wie Sie Status in einer Android-App speichern und verwenden. Dieser Leitfaden konzentriert sich auf die Verbindung zwischen Status und zusammensetzbaren Funktionen sowie auf die APIs, die Jetpack Compose für die einfachere Arbeit mit Status anbietet.

Status und Zusammensetzung

Die Funktion „Compose“ ist deklarativ. Daher kann sie nur aktualisiert werden, indem dieselbe zusammensetzbare Funktion mit neuen Argumenten aufgerufen wird. Diese Argumente sind Darstellungen des UI-Status. Immer, wenn ein Status aktualisiert wird, findet eine Neuzusammensetzung statt. Deshalb werden Dinge wie TextField nicht automatisch wie bei imperativen XML-basierten Ansichten aktualisiert. Einer zusammensetzbaren Funktion muss explizit der neue Status 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 Befehl ausführen und versuchen, Text einzugeben, passiert nichts. Das liegt daran, dass TextField nicht automatisch aktualisiert wird, sondern aktualisiert wird, wenn sich der value-Parameter ändert. Dies liegt an der Zusammensetzung und Neuzusammensetzung in der Funktion „Compose“.

Weitere Informationen zur anfänglichen Zusammensetzung und Neuzusammensetzung finden Sie unter In der Nachricht 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 anfänglichen Zusammensetzung in der Zusammensetzung gespeichert und während der Neuzusammensetzung zurückgegeben. Mit remember können sowohl änderbare als auch unveränderliche Objekte gespeichert werden.

mutableStateOf erstellt einen beobachtbaren MutableState<T>. Dies ist ein beobachtbarer Typ, der in die compose-Laufzeit eingebunden 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 werden als Syntaxzucker für verschiedene Verwendungen des Zustands angegeben. Wählen Sie diejenige aus, die in der von Ihnen geschriebenen zusammensetzbaren Funktion den am einfachsten zu lesenden Code erzeugt.

Für die Delegate-Syntax by sind folgende 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 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 über Konfigurationsänderungen hinweg beibehalten. Dazu müssen Sie rememberSaveable verwenden. rememberSaveable speichert automatisch jeden Wert, der in einem Bundle gespeichert werden kann. Für andere Werte können Sie ein benutzerdefiniertes Saver-Objekt übergeben.

Andere unterstützte Statustypen

Für das Composer-Programm müssen Sie den Status nicht mit MutableState<T> speichern. Es unterstützt andere beobachtbare Typen. Bevor Sie einen anderen beobachtbaren Typ in Composer lesen, müssen Sie ihn in einen State<T> konvertieren, damit zusammensetzbare Funktionen automatisch neu zusammengesetzt werden können, wenn sich der Status ändert.

Compose enthält Funktionen zum Erstellen von State<T> aus gängigen beobachtbaren Typen, die in Android-Apps verwendet werden. Fügen Sie die entsprechenden Artefakte hinzu, bevor Sie diese Integrationen verwenden:

  • Flow: collectAsStateWithLifecycle()

    collectAsStateWithLifecycle() erfasst Werte aus einem Flow unter Berücksichtigung des Lebenszyklus, sodass Ihre Anwendung die Anwendungsressourcen schont. Er steht für den letzten ausgegebenen Wert aus dem Befehl „Compose“ State. Verwenden Sie diese API als empfohlene Methode zum Erfassen von Abläufen in Android-Apps.

    Die folgende Abhängigkeit ist in der Datei build.gradle erforderlich (muss 2.6.0-beta01 oder höher sein):

Kotlin

dependencies {
      ...
      implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
}

Groovig

dependencies {
      ...
      implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.2"
}
  • Flow: collectAsState()

    collectAsState ähnelt collectAsStateWithLifecycle, da auch Werte aus einer Flow erfasst und in die Schaltfläche zum Erstellen State umgewandelt werden.

    Verwenden Sie collectAsState für plattformunabhängigen Code anstelle von collectAsStateWithLifecycle (nur Android).

    Für collectAsState sind keine zusätzlichen Abhängigkeiten erforderlich, da die Funktion in compose-runtime verfügbar ist.

  • LiveData: observeAsState()

    observeAsState() beobachtet dieses LiveData und stellt seine Werte über State dar.

    Die folgende Abhängigkeit ist in der Datei build.gradle erforderlich:

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-livedata:1.6.1")
}

Groovig

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-livedata:1.6.1"
}

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.6.1")
}

Groovig

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.6.1"
}

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava3:1.6.1")
}

Groovig

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava3:1.6.1"
}

Zustandsorientiert im Vergleich zu 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 Status name intern enthält 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. 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 hat. Zustandslose Lösungen können auf einfache Weise mit Zustandshebungen erreicht werden.

Wenn Sie wiederverwendbare zusammensetzbare Funktionen entwickeln, möchten Sie häufig sowohl eine zustandsorientierte als auch eine zustandslose Version derselben zusammensetzbaren Funktion verfügbar machen. Die zustandsorientierte Version ist praktisch für Aufrufer, denen der Zustand nicht wichtig ist. Die zustandslose Version ist für Aufrufer erforderlich, die den Status steuern oder hochziehen müssen.

Staatliches Hochziehen

Das Hochstufen des Zustands in Compose ist ein Muster, bei dem der Status zum Aufrufer einer zusammensetzbaren Funktion verschoben wird, um eine zusammensetzbare Funktion zustandslos zu machen. Das allgemeine Muster für Zustandswinden in Jetpack Compose besteht darin, die Statusvariable durch zwei Parameter zu ersetzen:

  • value: T: der aktuell anzuzeigende Wert
  • onValueChange: (T) -> Unit:Ereignis, bei dem die Änderung des Werts angefordert wird, wobei T der 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 mithilfe von Lambdas definieren.

Der Bundesstaat, der auf diese Weise aufgezogen wird, hat einige wichtige Eigenschaften:

  • Single Source of Truth:Indem wir den Status verschieben, statt ihn zu duplizieren, stellen wir sicher, dass es nur eine zentrale Informationsquelle gibt. So lassen sich Programmfehler vermeiden.
  • Kapselt:Nur zustandsorientierte zusammensetzbare Funktionen können ihren Status ändern. Es ist völlig intern.
  • Teilbar: Der Status „Hochgezogen“ kann für mehrere zusammensetzbare Funktionen verwendet werden. Wenn Sie name in einer anderen zusammensetzbaren Funktion lesen möchten, ist das mit Winden möglich.
  • Interceptable: 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 an einem beliebigen Ort gespeichert werden. Beispielsweise ist es jetzt möglich, name in eine ViewModel zu verschieben.

Im Beispiel extrahieren Sie die name und die onValueChange aus HelloContent und verschieben sie in der Baumstruktur nach oben in eine zusammensetzbare Funktion HelloScreen, 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 Zustand aus HelloContent hochziehen, ist es einfacher, die zusammensetzbare Funktion nachzuvollziehen, 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 bei der Entkopplung nicht ändern.

Das Muster, bei dem der Zustand abnimmt und die Ereignisse ansteigen, wird als unidirektionaler Datenfluss bezeichnet. In diesem Fall sinkt der Status von HelloScreen auf HelloContent und die Ereignisse steigen von HelloContent auf HelloScreen. Durch Befolgen des unidirektionalen Datenflusses 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 Windenstatus.

Status in „Compose“ wird wiederhergestellt

Die rememberSaveable API verhält sich ähnlich wie remember, weil sie den Status bei Neuzusammensetzungen und auch bei der Aktivitäts- oder Prozessneuerstellung unter Verwendung des gespeicherten Instanzstatusmechanismus beibehält. Das ist beispielsweise der Fall, wenn der Bildschirm gedreht wird.

Möglichkeiten zum Speichern des Status

Alle Datentypen, die der Bundle hinzugefügt werden, werden automatisch gespeichert. Wenn Sie etwas speichern möchten, das dem Bundle nicht hinzugefügt werden kann, haben Sie mehrere Möglichkeiten.

Parieren

Die einfachste Lösung besteht darin, dem Objekt die Annotation @Parcelize hinzuzufügen. Das Objekt wird parzelable und kann gebündelt werden. Durch diesen Code wird beispielsweise ein parzelabler City-Datentyp erstellt und im Bundesstaat 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 nicht geeignet ist, können Sie mit mapSaver eine eigene Regel zum Konvertieren 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"))
    }
}

Listenspeicherung

Wenn Sie die Schlüssel für die Karte nicht definieren müssen, können Sie auch listSaver verwenden und seine 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"))
    }
}

Statusinhaber in Compose

Einfache Hebezüge können in den zusammensetzbaren Funktionen selbst verwaltet werden. Wenn jedoch die Logik zur Durchführung in zusammensetzbaren Funktionen ermittelt wird, empfiehlt es sich, die logischen und staatlichen Verantwortlichkeiten an andere Klassen zu delegieren: Inhaber von Bundesstaaten.

Weitere Informationen finden Sie in der Dokumentation zum Zustandswinden in der Compose-Dokumentation oder allgemeiner auf der Seite Zustandsinhaber und UI-Status im Architekturleitfaden.

Berechnungen neu auslösen, wenn Tasten sich ändern

Die remember API wird häufig zusammen mit MutableState verwendet:

var name by remember { mutableStateOf("") }

Hier sorgt die Funktion remember dafür, dass der MutableState-Wert Neuzusammensetzungen überdauert.

Im Allgemeinen verwendet remember einen Lambda-Parameter calculation. Wenn remember zum ersten Mal ausgeführt wird, ruft es das Lambda calculation auf und speichert das Ergebnis. Bei der Neuzusammensetzung gibt remember den Wert zurück, der zuletzt gespeichert wurde.

Neben dem Caching-Status können Sie auch remember verwenden, um Objekte oder Ergebnisse eines Vorgangs in der Zusammensetzung zu speichern, deren Initialisierung oder Berechnung aufwendig ist. Sie sollten diese Berechnung nicht bei jeder Neuzusammensetzung wiederholen. Ein Beispiel hierfür 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 verwendet auch den Parameter key oder keys. Wenn sich einer dieser Schlüssel ändert, wird bei der nächsten Neuzusammensetzung remember der Cache ungültig und die Berechnung wird noch einmal mit dem Lambda-Block ausgeführt. Dieser Mechanismus gibt Ihnen die Kontrolle über die Lebensdauer eines Objekts in der Komposition. Die Berechnung bleibt so lange gültig, bis sich die Eingaben ändern, und nicht so lange, 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 Hintergrundfarbe einer zusammensetzbaren Funktion Box verwendet. remember speichert die ShaderBrush-Instanz, da die Neuerstellung teuer ist, wie oben erläutert. remember verwendet avatarRes als key1-Parameter. Das ist das ausgewählte Hintergrundbild. Wenn sich avatarRes ändert, wird der Pinsel mit dem neuen Bild neu zusammengesetzt und auf Box angewendet. Das kann vorkommen, 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 Zustand in eine einfache Zustands-Holder-Klasse MyAppState hochgezogen. Es stellt eine rememberMyAppState-Funktion zur Verfügung, um eine Instanz der Klasse mit remember zu initialisieren. Das Verfügbarmachen solcher Funktionen zum Erstellen einer Instanz, die Neuzusammensetzungen ü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 Anwendung die Nur-Zustands-Holder-Klasse mit dem neuesten Wert neu erstellen. Das kann beispielsweise der Fall sein, 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.

Status mit Schlüsseln über Neuzusammensetzung hinaus speichern

Die rememberSaveable API ist ein Wrapper um remember, mit dem Daten in einem Bundle gespeichert werden können. Diese API ermöglicht es dem Zustand, nicht nur die Neuzusammensetzung, sondern auch die Wiederherstellung von Aktivitäten und das vom System initiierte Tod von Prozessen zu überleben. rememberSaveable empfängt input-Parameter für denselben Zweck wie remember keys. Der Cache wird entwertet, wenn sich eine der Eingaben ändert. Bei der nächsten Neuzusammensetzung der Funktion 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 zu State und Jetpack Compose finden Sie in den folgenden zusätzlichen Ressourcen.

Produktproben

Codelabs

Videos

Blogs