In Compose ist die Benutzeroberfläche unveränderlich. Sie kann nach dem Zeichnen nicht mehr aktualisiert werden. Sie können lediglich den Status der Benutzeroberfläche steuern. Jedes Mal, wenn sich der Status der
Benutzeroberfläche ändert, erstellt Compose die Teile der Benutzeroberflächenstruktur neu, die sich geändert haben. Composables können Status akzeptieren und Ereignisse bereitstellen. Ein TextField akzeptiert beispielsweise einen Wert und stellt einen Callback onValueChange bereit, der den Callback-Handler auffordert, den Wert zu ändern.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
Da Composables Status akzeptieren und Ereignisse bereitstellen, passt das Muster des unidirektionalen Datenflusses gut zu Jetpack Compose. In diesem Leitfaden wird beschrieben, wie Sie das Muster des unidirektionalen Datenflusses in Compose implementieren, wie Sie Ereignis- und Status-Holder implementieren und wie Sie mit ViewModels in Compose arbeiten.
Unidirektionaler Datenfluss
Ein unidirektionaler Datenfluss (Unidirectional Data Flow, UDF) ist ein Designmuster, bei dem der Status nach unten und Ereignisse nach oben fließen. Wenn Sie den unidirektionalen Datenfluss verwenden, können Sie Composables, die den Status in der Benutzeroberfläche anzeigen, von den Teilen Ihrer App entkoppeln, die den Status speichern und ändern.
Der UI-Aktualisierungszyklus für eine App mit unidirektionalem Datenfluss sieht so aus:
- Ereignis: Ein Teil der Benutzeroberfläche generiert ein Ereignis und übergibt es nach oben, z. B. ein Klick auf eine Schaltfläche, der an das ViewModel übergeben wird, um verarbeitet zu werden. Oder ein Ereignis wird von anderen Ebenen Ihrer App übergeben, z. B. um anzugeben, dass die Nutzersitzung abgelaufen ist.
- Status aktualisieren: Ein Event-Handler kann den Status ändern.
- Status anzeigen: Der Status-Holder übergibt den Status nach unten und die Benutzeroberfläche zeigt ihn an.
Die Verwendung dieses Musters mit Jetpack Compose bietet mehrere Vorteile:
- Testbarkeit: Wenn Sie den Status von der Benutzeroberfläche entkoppeln, die ihn anzeigt, können Sie beides einfacher isoliert testen.
- Zustands-Datenkapselung: Da der Zustand nur an einer Stelle aktualisiert werden kann und es nur eine Quelle der Wahrheit für den Zustand einer komponierbaren Funktion gibt, ist es weniger wahrscheinlich, dass Sie Fehler aufgrund inkonsistenter Zustände erstellen.
- UI-Konsistenz: Alle Statusaktualisierungen werden sofort in der Benutzeroberfläche widergespiegelt, indem
beobachtbare Status-Holder wie
StateFlowoderLiveDataverwendet werden.
Unidirektionaler Datenfluss in Jetpack Compose
Composables funktionieren basierend auf Status und Ereignissen. Ein TextField wird beispielsweise nur aktualisiert, wenn der Parameter value aktualisiert wird. Außerdem wird ein onValueChange-Callback bereitgestellt, ein Ereignis, das anfordert, dass der Wert in einen neuen Wert geändert wird. In Compose wird das Objekt State als Wert-Holder 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 Sie sich an den Wert erinnern müssen.
Der Typ des Werts des TextField-Composables ist String. Dieser kann von überall stammen – von einem fest codierten Wert, von einem ViewModel oder vom übergeordneten Composables. Sie müssen ihn nicht in einem State-Objekt speichern, aber Sie müssen den Wert aktualisieren, wenn onValueChange aufgerufen wird.
Composables-Parameter definieren
Beachten Sie beim Definieren der Statusparameter eines Composables die folgenden Fragen:
- Wie wiederverwendbar oder flexibel ist das Composables?
- Wie wirken sich die Statusparameter auf die Leistung dieses Composables aus?
Um die Entkopplung und Wiederverwendung zu fördern, sollte jedes Composables so wenig Informationen wie möglich enthalten. Wenn Sie beispielsweise ein Composables erstellen, um die Kopfzeile eines Nachrichtenartikels zu speichern, sollten Sie nur die Informationen übergeben, die angezeigt werden müssen, und nicht den gesamten Nachrichtenartikel:
@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 auch die Leistung. Wenn
News beispielsweise mehr Informationen als nur title und subtitle enthält, wird das Composables bei jeder Übergabe einer
neuen Instanz von News an Header(news) neu zusammengesetzt, auch wenn sich title und subtitle nicht geändert haben.
Überlegen Sie sich genau, wie viele Parameter Sie übergeben. Eine Funktion mit zu vielen Parametern ist nicht benutzerfreundlich. In diesem Fall 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. Da diese Ereignisse den Status der Benutzeroberfläche ändern, sollte das ViewModel sie verarbeiten und den UI-Status aktualisieren.
Die UI-Ebene sollte den Zustand niemals außerhalb eines Event-Handlers ändern, da dies zu Inkonsistenzen und Fehlern in Ihrer Anwendung führen kann.
Übergeben Sie möglichst unveränderliche Werte für Zustand- und Event-Handler-Lambdas. Dieser Ansatz hat folgende Vorteile:
- Sie verbessern die Wiederverwendbarkeit.
- Sie stellen sicher, dass die Benutzeroberfläche den Wert des Status nicht direkt ändert.
- Sie vermeiden Probleme mit der Nebenläufigkeit, da Sie sicherstellen, dass der Zustand nicht von einem anderen Thread aus geändert wird.
- Oft reduzieren Sie die Codekomplexität.
Ein Composables, das einen String und ein Lambda als Parameter akzeptiert, kann beispielsweise in vielen Kontexten aufgerufen werden und ist sehr wiederverwendbar. Angenommen, die obere App-Leiste in Ihrer App zeigt immer Text an und hat eine Schaltfläche „Zurück“. Sie können eine allgemeinere MyAppTopAppBar-komponierbare Funktion definieren, die den Text und den Handler für den Button „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 einführen, wenn eine der folgenden Bedingungen zutrifft:
- Der Status der Benutzeroberfläche wird mit beobachtbaren Status-Holdern wie
StateFlowoderLiveDatabereitgestellt. - Das
ViewModelverarbeitet Ereignisse, die von der Benutzeroberfläche oder anderen Ebenen Ihrer App stammen, und aktualisiert den Status-Holder basierend auf den Ereignissen.
Wenn Sie beispielsweise einen Anmeldebildschirm implementieren, sollte durch Tippen auf die Schaltfläche Anmelden ein rotierendes Ladesymbol und ein Netzwerkaufruf angezeigt werden. Wenn die Anmeldung erfolgreich war, wird in Ihrer App ein anderer Bildschirm aufgerufen. Im Fehlerfall wird eine Snackbar angezeigt. So modellieren Sie den Bildschirmstatus und das Ereignis:
Der Bildschirm hat vier Zustände:
- Abgemeldet: wenn der Nutzer sich noch nicht angemeldet hat.
- In Bearbeitung: wenn Ihre App versucht, den Nutzer anzumelden, indem sie einen Netzwerkaufruf ausführt.
- Fehler: wenn bei der Anmeldung ein Fehler aufgetreten ist.
- Angemeldet: wenn der Nutzer angemeldet ist.
Sie können diese Status als versiegelte Klasse modellieren. Das ViewModel stellt den Status als State bereit, legt den Anfangsstatus fest und aktualisiert den Status nach Bedarf. Das ViewModel verarbeitet auch das Anmeldeereignis, indem es eine onSignIn()-Methode bereitstellt.
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
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:
Beispiele
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist
- Status und Jetpack Compose
- UI-Status in Compose speichern
- Nutzereingabe verarbeiten