Benutzeroberfläche für das Schreiben erstellen

In „Compose“ ist die UI unveränderlich. Sie kann nach dem Zeichnen nicht mehr aktualisiert werden. Sie können nur den Status Ihrer UI steuern. Jedes Mal, wenn sich der Status der UI ändert, werden bei Compose die geänderten Teile der UI-Struktur neu erstellt. Zusammensetzbare Funktionen können Statuswerte akzeptieren und Ereignisse offenlegen. Ein TextField akzeptiert beispielsweise einen Wert und stellt eine Callback-onValueChange bereit, die den Callback-Handler auffordert, den Wert zu ändern.

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

Da zusammensetzbare Funktionen Zustands- und Freigabeereignisse akzeptieren, passt das Muster des unidirektionalen Datenflusses gut zu Jetpack Compose. In diesem Leitfaden erfahren Sie, wie Sie das Muster des unidirektionalen Datenflusses in Compose implementieren, Ereignisse und Statusinhaber implementieren und in Compose mit ViewModels arbeiten.

Unidirektionaler Datenfluss

Ein unidirektionaler Datenfluss (UDF) ist ein Designmuster, bei dem der Zustand nach unten und die Ereignisse nach oben fließen. 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.

Die Aktualisierungsschleife der Benutzeroberfläche für eine App mit unidirektionalem Datenfluss sieht so aus:

  • Ereignis: Ein Teil der UI generiert ein Ereignis und übergibt es nach oben. Das kann beispielsweise ein Klick auf eine Schaltfläche sein, der zur Verarbeitung an die ViewModel übergeben wird. Es kann auch ein Ereignis von anderen Ebenen Ihrer App übergeben werden, um beispielsweise anzugeben, dass die Nutzersitzung abgelaufen ist.
  • Aktualisierungsstatus: Ein Event-Handler kann den Status ändern.
  • Anzeigestatus: Der Statusinhaber gibt den Status weiter und wird auf der UI angezeigt.

Abbildung 1. Unidirektionaler Datenfluss.

Dieses Muster bei der Verwendung von Jetpack Compose bietet mehrere Vorteile:

  • Testbarkeit: Durch die Entkopplung des Status von der angezeigten Benutzeroberfläche lassen sich beide Elemente leichter isoliert testen.
  • Datenkapselung: Da der Status nur an einem Ort aktualisiert werden kann und es nur eine zentrale Datenquelle für den Status einer zusammensetzbaren Funktion gibt, ist es weniger wahrscheinlich, dass es aufgrund von inkonsistenten Status zu Fehlern kommt.
  • UI-Konsistenz: Alle Statusaktualisierungen werden sofort in der UI angezeigt, wenn beobachtbare Statusinhaber wie StateFlow oder LiveData verwendet werden.

Unidirektionaler Datenfluss in Jetpack Compose

Zusammensetzbare Funktionen basieren auf Status und Ereignissen. Ein TextField-Objekt wird beispielsweise nur dann aktualisiert, wenn sein value-Parameter aktualisiert wird und ein onValueChange-Callback verfügbar ist – ein Ereignis, bei dem die Änderung des Werts in einen neuen Wert angefordert wird. Bei der Zusammensetzung wird das State-Objekt als Wertinhaber definiert. Änderungen am Statuswert lösen eine Neuzusammensetzung aus. Sie können den Status in einem remember { mutableStateOf(value) }- oder rememberSaveable { mutableStateOf(value)-Objekt enthalten, je nachdem, wie lange Sie sich den Wert merken müssen.

Der Typ der zusammensetzbaren Funktion TextField ist String. Dieser kann also von überall stammen – von einem hartcodierten Wert, aus einem ViewModel oder aus der übergeordneten zusammensetzbaren Funktion. Sie müssen ihn nicht in einem State-Objekt enthalten, müssen aber den Wert aktualisieren, wenn onValueChange aufgerufen wird.

Zusammensetzbare Parameter definieren

Beachten Sie beim Definieren der Statusparameter einer zusammensetzbaren Funktion die folgenden Fragen:

  • Wie wiederverwendbar oder flexibel ist die zusammensetzbare Funktion?
  • Wie wirken sich die Zustandsparameter auf die Leistung dieser zusammensetzbaren Funktion aus?

Um Entkopplung und Wiederverwendung zu fördern, sollte jede zusammensetzbare Funktion so wenig Informationen wie möglich enthalten. Wenn du beispielsweise eine zusammensetzbare Funktion für den Header eines Nachrichtenartikels erstellst, solltest du statt des gesamten Nachrichtenartikels nur die Informationen übergeben, die angezeigt werden müssen:

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

Manchmal verbessert die Verwendung einzelner Parameter die Leistung. Beispiel: Wenn News mehr Informationen als nur title und subtitle enthält und eine neue Instanz von News an Header(news) übergeben wird, wird die zusammensetzbare Funktion neu zusammengesetzt, auch wenn title und subtitle nicht geändert wurden.

Überlegen Sie sich genau, wie viele Parameter Sie übergeben. Eine Funktion mit zu vielen Parametern reduziert die Ergonomie der Funktion. Daher ist es besser, sie in einer Klasse zu gruppieren.

Ereignisse in Compose

Jede Eingabe in Ihre App sollte als Ereignis dargestellt werden: Tippen, Textänderungen und sogar Timer oder andere Aktualisierungen. Wenn sich durch diese Ereignisse der Status der UI ändert, sollte ViewModel sie verarbeiten und den UI-Status aktualisieren.

Die UI-Ebene sollte niemals den Status außerhalb eines Event-Handlers ändern, da dies zu Inkonsistenzen und Programmfehlern in Ihrer Anwendung führen kann.

Übergeben unveränderlicher Werte für Status- und Event-Handler-Lambdas bevorzugt. Dieser Ansatz bietet folgende Vorteile:

  • Sie verbessern die Wiederverwendbarkeit.
  • Achten Sie darauf, dass die Benutzeroberfläche den Wert des Status nicht direkt ändert.
  • Sie vermeiden Probleme mit der Nebenläufigkeit, da Sie dafür sorgen, dass der Status nicht von einem anderen Thread geändert wird.
  • Oft verringern Sie die Komplexität des Codes.

Beispielsweise kann eine zusammensetzbare Funktion, die String und Lambda als Parameter akzeptiert, aus vielen Kontexten aufgerufen werden und ist in hohem Maße wiederverwendbar. Angenommen, die obere App-Leiste in Ihrer App enthält immer Text und eine Schaltfläche „Zurück“. Sie können eine allgemeinere zusammensetzbare Funktion MyAppTopAppBar definieren, die den Text und das Handle der Schaltfläche „Zurück“ als Parameter empfängt:

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModels, Status und Ereignisse: Beispiel

Durch die Verwendung von ViewModel und mutableStateOf können Sie auch einen unidirektionalen Datenfluss in Ihre Anwendung einführen, wenn eine der folgenden Bedingungen zutrifft:

  • Der Status Ihrer UI wird über beobachtbare Statusinhaber wie StateFlow oder LiveData angegeben.
  • Der ViewModel verarbeitet Ereignisse, die von der UI oder anderen Ebenen Ihrer App stammen, und aktualisiert den Statusinhaber anhand der Ereignisse.

Wenn Sie beispielsweise bei der Implementierung eines Anmeldebildschirms auf die Schaltfläche Anmelden tippen, sollten in Ihrer App ein Fortschrittssymbol und ein Netzwerkaufruf angezeigt werden. Wenn die Anmeldung erfolgreich war, wechselt deine App zu einem anderen Bildschirm. Bei einem Fehler zeigt die App eine Snackbar an. So modellieren Sie den Bildschirmstatus und das Ereignis:

Der Bildschirm hat vier Status:

  • Abgemeldet: Der Nutzer hat sich noch nicht angemeldet.
  • In Bearbeitung: Ihre App versucht gerade, den Nutzer über einen Netzwerkaufruf anzumelden.
  • Fehler: Bei der Anmeldung ist ein Fehler aufgetreten.
  • Angemeldet: Der Nutzer ist angemeldet.

Sie können diese Zustände als versiegelte Klasse modellieren. Der ViewModel zeigt den Status als State an, legt den Anfangszustand fest und aktualisiert den Status nach Bedarf. ViewModel verarbeitet außerdem das Anmeldeereignis und stellt dazu eine onSignIn()-Methode bereit.

class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<UiState>(UiState.SignedOut)
    val uiState: State<UiState>
        get() = _uiState

    // ...
}

Zusätzlich zur mutableStateOf API bietet Compose Erweiterungen für LiveData, Flow und Observable, um sich als Listener zu registrieren und den Wert als Status darzustellen.

class MyViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UiState>(UiState.SignedOut)
    val uiState: LiveData<UiState>
        get() = _uiState

    // ...
}

@Composable
fun MyComposable(viewModel: MyViewModel) {
    val uiState = viewModel.uiState.observeAsState()
    // ...
}

Weitere Informationen

Weitere Informationen zur Architektur in Jetpack Compose finden Sie in den folgenden Ressourcen:

Produktproben