Benutzeroberfläche für das Schreiben erstellen

In Compose ist die Benutzeroberfläche unveränderlich. Sie kann nach dem Zeichnen nicht mehr aktualisiert werden. Sie können jedoch den Status Ihrer Benutzeroberfläche steuern. Jedes Mal, wenn sich der Status der Benutzeroberfläche ändert, erstellt Compose die geänderten Teile des UI-Baums neu. Composables können Status akzeptieren und Ereignisse freigeben. Beispiel: Ein TextField akzeptiert einen Wert und gibt einen Rückruf onValueChange aus, der den Rückruf-Handler auffordert, den Wert zu ändern.

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

Da Compose-Komponenten Status akzeptieren und Ereignisse freigeben, passt das Muster für den unidirektionalen Datenfluss gut zu Jetpack Compose. In diesem Leitfaden erfahren Sie, wie Sie das unidirektionale Datenflussmuster in Compose implementieren, Ereignisse und Statushalter implementieren und mit ViewModels in Compose arbeiten.

Einseitiger Datenfluss

Ein einseitiger Datenfluss (UDF) ist ein Designmuster, bei dem der Status nach unten und Ereignisse nach oben fließen. Wenn Sie einen unidirektionalen Datenfluss verwenden, können Sie Composeables, die den Status in der Benutzeroberfläche anzeigen, von den Teilen Ihrer App entkoppeln, die den Status speichern und ändern.

Die UI-Aktualisierungsschleife für eine App mit unidirektionalem Datenfluss sieht so aus:

  1. Ereignis: Ein Teil der Benutzeroberfläche generiert ein Ereignis und leitet es weiter, z. B. ein Klick auf eine Schaltfläche, der an das ViewModel zur Verarbeitung übergeben wird. Ein Ereignis kann auch von anderen Ebenen Ihrer App übergeben werden, z. B. um anzugeben, dass die Nutzersitzung abgelaufen ist.
  2. Status aktualisieren: Ein Ereignishandler kann den Status ändern.
  3. Displaystatus: Der Statushalter gibt den Status weiter und die Benutzeroberfläche zeigt ihn an.

Ereignisse fließen von der Benutzeroberfläche zu einem Statushalter und der Status fließt vom Statushalter zur Benutzeroberfläche.
Abbildung 1: Einseitiger Datenfluss.

Wenn Sie bei der Verwendung von Jetpack Compose diesem Muster folgen, ergeben sich mehrere Vorteile:

  • Testbarkeit: Wenn der Status von der Benutzeroberfläche getrennt wird, auf der er angezeigt wird, lässt sich beides leichter einzeln testen.
  • Statuskapselung: Da der Status nur an einer Stelle aktualisiert werden kann und es nur eine einzige Quelle für den Status eines Composeables gibt, ist es weniger wahrscheinlich, dass Sie aufgrund inkonsistenter Status Fehler verursachen.
  • UI-Konsistenz: Alle Statusaktualisierungen werden durch die Verwendung von beobachtbaren Statushaltern wie StateFlow oder LiveData sofort in der Benutzeroberfläche angezeigt.

Einseitiger Datenfluss in Jetpack Compose

Composables basieren auf Status und Ereignissen. Ein TextField wird beispielsweise nur aktualisiert, wenn sein value-Parameter aktualisiert wird und ein onValueChange-Callback vorhanden ist, also ein Ereignis, bei dem der Wert in einen neuen geändert werden soll. Mit „Compose“ wird das State-Objekt als Werthalter definiert. Änderungen am Statuswert lösen eine Neuzusammensetzung aus. Sie können den Status in einem remember { mutableStateOf(value) } oder einem rememberSaveable { mutableStateOf(value) speichern, je nachdem, wie lange der Wert gespeichert werden muss.

Der Wert des TextField-Composables hat den Typ String. Er kann also von überall stammen – von einem hartcodierten Wert, von einem ViewModel oder vom übergeordneten Composable übergeben werden. Sie müssen ihn nicht in einem State-Objekt speichern, aber Sie müssen den Wert aktualisieren, wenn onValueChange aufgerufen wird.

Zusammensetzbare Parameter definieren

Beachten Sie beim Definieren der Statusparameter eines Composeables Folgendes:

  • Wie wiederverwendbar oder flexibel ist das Composeable?
  • Wie wirken sich die Statusparameter auf die Leistung dieses Composeables aus?

Um die Entkopplung und Wiederverwendung zu fördern, sollte jedes Composeable möglichst wenig Informationen enthalten. Wenn du beispielsweise ein Composeable erstellst, das den Titel eines Nachrichtenartikels enthalten soll, solltest du nur die Informationen übergeben, die angezeigt werden sollen, und nicht den gesamten Artikel:

@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 lässt sich die Leistung auch durch die Verwendung einzelner Parameter verbessern. Wenn News beispielsweise mehr Informationen als nur title und subtitle enthält, wird das Composeable jedes Mal neu erstellt, wenn eine neue Instanz von News an Header(news) übergeben wird, auch wenn sich title und subtitle nicht geändert haben.

Überlegen Sie sich gut, wie viele Parameter Sie übergeben. Eine Funktion mit zu vielen Parametern verringert die Ergonomie der Funktion. In diesem Fall ist es daher 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. Da diese Ereignisse den Status Ihrer Benutzeroberfläche ändern, sollte die ViewModel diese Ereignisse verarbeiten und den UI-Status aktualisieren.

Der Status der UI-Ebene sollte niemals außerhalb eines Ereignis-Handlers geändert werden, da dies zu Inkonsistenzen und Fehlern in Ihrer Anwendung führen kann.

Geben Sie für Status- und Ereignishandler-Lambdas vorzugsweise unveränderliche Werte weiter. Dieser Ansatz bietet folgende Vorteile:

  • Sie verbessern die Wiederverwendbarkeit.
  • Sie sorgen dafür, dass der Wert des Status nicht direkt über die Benutzeroberfläche geändert wird.
  • Sie vermeiden Probleme mit der Parallelität, da Sie dafür sorgen, dass der Status nicht von einem anderen Thread verändert wird.
  • Oft wird dadurch die Codekomplexität reduziert.

Ein Composeable, das beispielsweise eine String und ein Lambda als Parameter akzeptiert, kann aus vielen Kontexten aufgerufen werden und ist sehr wiederverwendbar. Angenommen, die obere App-Leiste in Ihrer App enthält immer Text und eine Schaltfläche „Zurück“. Sie können ein allgemeineres MyAppTopAppBar-Komposit definieren, das den Text und den 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.AutoMirrored.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModels, Status und Ereignisse: ein Beispiel

Mit ViewModel und mutableStateOf können Sie auch einen unidirektionalen Datenfluss in Ihrer App einrichten, wenn eines der folgenden Kriterien erfüllt ist:

  • Der Status Ihrer Benutzeroberfläche wird über beobachtbare Statushalter wie StateFlow oder LiveData freigegeben.
  • Der ViewModel verarbeitet Ereignisse, die von der Benutzeroberfläche oder anderen Ebenen Ihrer App stammen, und aktualisiert den Statushalter basierend auf den Ereignissen.

Wenn Sie beispielsweise einen Anmeldebildschirm implementieren, sollte durch Tippen auf die Schaltfläche Anmelden in Ihrer App ein Fortschrittskreisel und ein Netzwerkaufruf angezeigt werden. Wenn die Anmeldung erfolgreich war, wird in Ihrer App ein anderer Bildschirm angezeigt. Bei einem Fehler wird eine Snackbar angezeigt. So modellieren Sie den Bildschirmstatus und das Ereignis:

Der Bildschirm hat vier Status:

  • Abgemeldet: Der Nutzer ist noch nicht angemeldet.
  • In Bearbeitung: Ihre App versucht derzeit, den Nutzer durch einen Netzwerkaufruf anzumelden.
  • Fehler: Bei der Anmeldung ist ein Fehler aufgetreten.
  • Angemeldet: Der Nutzer ist angemeldet.

Sie können diese Status als versiegelte Klasse modellieren. Die ViewModel stellt den Status als State bereit, legt den Anfangsstatus fest und aktualisiert den Status nach Bedarf. Die ViewModel verarbeitet auch das Anmeldeereignis, indem eine onSignIn()-Methode freigegeben wird.

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