Die Aufgabe der Benutzeroberfläche besteht darin, die Anwendungsdaten auf dem Bildschirm anzuzeigen. Die Benutzeroberfläche ist auch der primäre Punkt für Nutzerinteraktionen. Immer wenn sich die Daten ändern, entweder durch Nutzerinteraktion (z. B. durch Drücken einer Schaltfläche) oder durch externe Eingaben (z. B. eine Netzwerkantwort), wird die Benutzeroberfläche entsprechend aktualisiert. Die Benutzeroberfläche ist im Grunde eine visuelle Darstellung des Anwendungsstatus, der aus der Datenschicht abgerufen wird.
Die Anwendungsdaten, die Sie aus der Datenschicht erhalten, haben jedoch in der Regel ein anderes Format als die Informationen, die Sie anzeigen müssen. Möglicherweise benötigen Sie beispielsweise nur einen Teil der Daten für die Benutzeroberfläche oder müssen zwei verschiedene Datenquellen zusammenführen, um für den Nutzer relevante Informationen zu präsentieren. Unabhängig von der angewendeten Logik müssen Sie der Benutzeroberfläche alle Informationen übergeben, die für das vollständige Rendern erforderlich sind. Die UI-Ebene ist die Pipeline, die Änderungen an Anwendungsdaten in ein Format umwandelt, das die Benutzeroberfläche darstellen kann, und sie dann anzeigt.
Eine einfache Fallstudie
Stellen Sie sich eine App vor, die Nachrichtenartikel für einen Nutzer abruft. Die App hat einen Artikelbildschirm, auf dem verfügbare Artikel angezeigt werden. Angemeldete Nutzer können außerdem besonders interessante Artikel mit einem Lesezeichen versehen. Da es jederzeit viele Artikel geben kann, muss der Leser Artikel nach Kategorie durchsuchen können. Zusammenfassend lässt sich sagen, dass Nutzer mit der App Folgendes tun können:
- Artikel ansehen, die gelesen werden können
- Artikel nach Kategorie durchsuchen
- Melden Sie sich an und speichern Sie bestimmte Artikel als Lesezeichen.
- Zugriff auf einige Premium-Funktionen, sofern berechtigt
In den folgenden Abschnitten wird dieses Beispiel als Fallstudie verwendet, um die Prinzipien des unidirektionalen Datenflusses vorzustellen und die Probleme zu veranschaulichen, die diese Prinzipien im Kontext der App-Architektur für die UI-Schicht lösen.
Architektur der UI-Ebene
Der Begriff Benutzeroberfläche bezieht sich auf UI-Elemente wie Container und komponierbare Funktionen, die Daten anzeigen. Für die Entwicklung von Android-Benutzeroberflächen empfehlen wir das Toolkit Jetpack Compose. Da die Datenebene dazu dient, auf die App-Daten zuzugreifen, sie zu verwalten und bereitzustellen, muss die UI-Ebene die folgenden Schritte ausführen:
- App-Daten verwenden und in Daten umwandeln, die von der Benutzeroberfläche einfach gerendert werden können.
- UI-renderbare Daten aufnehmen und in UI-Elemente für die Darstellung für den Nutzer umwandeln.
- Nutzereingabeereignisse aus diesen zusammengesetzten UI-Elementen verarbeiten und ihre Auswirkungen bei Bedarf in den UI-Daten berücksichtigen.
- Wiederholen Sie die Schritte 1 bis 3 so lange wie nötig.
Im Rest dieses Leitfadens wird gezeigt, wie Sie eine UI-Ebene implementieren, die diese Schritte ausführt. In diesem Leitfaden werden insbesondere die folgenden Aufgaben und Konzepte behandelt:
- UI-Status definieren
- Unidirektionaler Datenfluss (UDF) als Mittel zum Erstellen und Verwalten des UI-Zustands
- UI-Status mit beobachtbaren Datentypen gemäß UDF-Prinzipien verfügbar machen
- UI implementieren, die den beobachtbaren UI-Status nutzt
Die grundlegendste davon ist die Definition des UI-Zustands.
UI-Status definieren
In der Fallstudie, die weiter oben beschrieben wird, wird in der Benutzeroberfläche eine Liste von Artikeln zusammen mit einigen Metadaten für jeden Artikel angezeigt. Diese Informationen, die die App dem Nutzer präsentiert, sind der UI-Zustand.
Mit anderen Worten: Wenn die Benutzeroberfläche das ist, was der Nutzer sieht, ist der Benutzeroberflächenstatus das, was die App ihm anzeigen sollte. Die Benutzeroberfläche ist wie die zwei Seiten einer Medaille die visuelle Darstellung des UI-Zustands. Alle Änderungen am UI-Status werden sofort in der Benutzeroberfläche angezeigt.
Betrachten Sie die Fallstudie: Um die Anforderungen der News-App zu erfüllen, können die Informationen, die zum vollständigen Rendern der Benutzeroberfläche erforderlich sind, in einer NewsUiState-Datenklasse gekapselt werden, die so definiert ist:
data class NewsUiState(
val isSignedIn: Boolean = false,
val isPremium: Boolean = false,
val newsItems: List<NewsItemUiState> = listOf(),
val userMessages: List<Message> = listOf()
)
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
...
)
Weitere Informationen zum UI-Status finden Sie unter State und Jetpack Compose.
Unveränderlichkeit
Die Definition des UI-Zustands im vorherigen Beispiel ist unveränderlich. Der Hauptvorteil besteht darin, dass unveränderliche Objekte Garantien für den Zustand der Anwendung zu einem bestimmten Zeitpunkt bieten. So kann sich die Benutzeroberfläche auf ihre primäre Rolle konzentrieren: den Status lesen und die UI-Elemente entsprechend aktualisieren. Ändern Sie den UI-Status niemals direkt in der Benutzeroberfläche, es sei denn, die Benutzeroberfläche selbst ist die einzige Quelle ihrer Daten. Ein Verstoß gegen diesen Grundsatz führt zu mehreren Wahrheitsquellen für dieselben Informationen, was zu Dateninkonsistenzen und subtilen Fehlern führt.
Sehen Sie sich beispielsweise die Fallstudie oben an.
Wenn das bookmarked-Flag in einem NewsItemUiState-Objekt aus dem UI-Zustand in der Activity-Klasse aktualisiert wird, konkurriert dieses Flag mit der Datenschicht als Quelle für den Status „Mit Lesezeichen versehen“ eines Artikels. Unveränderliche Datenklassen sind sehr nützlich, um diese Art von Inkonsistenz zu vermeiden.
Namenskonventionen in diesem Leitfaden
In dieser Anleitung werden UI-Statusklassen nach der Funktionalität des Bildschirms oder des Teils des Bildschirms benannt, den sie beschreiben. Die Konvention lautet so:
functionality + UiState.
Der Status eines Bildschirms, auf dem Nachrichten angezeigt werden, könnte beispielsweise NewsUiState heißen und der Status eines Nachrichteneintrags in einer Liste von Nachrichteneinträgen könnte NewsItemUiState sein.
Status mit unidirektionalem Datenfluss verwalten
Im vorherigen Abschnitt wurde festgestellt, dass der UI-Zustand ein unveränderlicher Snapshot der Details ist, die für das Rendern der UI erforderlich sind. Da Daten in Apps jedoch dynamisch sind, kann sich der Status im Laufe der Zeit ändern. Das kann an Nutzerinteraktionen oder anderen Ereignissen liegen, die die zugrunde liegenden Daten ändern, mit denen die App gefüllt wird.
Für die Verarbeitung dieser Interaktionen kann ein Mediator verwendet werden, der die Logik für jedes Ereignis definiert und die zugrunde liegenden Datenquellen transformiert, um den UI-Status zu erstellen. Obwohl diese Interaktionen und ihre Logik in der Benutzeroberfläche selbst untergebracht werden können, kann dies schnell unübersichtlich werden, da die Benutzeroberfläche zu viele Aufgaben übernimmt. Außerdem kann dies die Testbarkeit beeinträchtigen, da der resultierende Code eng gekoppelt ist. Sofern der UI-Status nicht sehr einfach ist, sollte die UI nur für die Verarbeitung und Anzeige des UI-Status zuständig sein.
In diesem Abschnitt wird das Architekturmuster „Unidirectional Data Flow“ (UDF) beschrieben, das dabei hilft, diese Trennung der Verantwortlichkeiten zu erzwingen.
State Holder
State-Holder sind die Klassen, die für die Erstellung des UI-Zustands und für die Logik verantwortlich sind, die zum Erstellen dieses Zustands erforderlich ist. State-Holder sind je nach Umfang der entsprechenden UI-Elemente, die sie verwalten, unterschiedlich groß. Sie können von einem einzelnen Widget wie einer unteren App-Leiste bis hin zu einem ganzen Bildschirm oder einem Navigationsziel reichen.
Im letzteren Fall ist die typische Implementierung eine Instanz eines ViewModel. Je nach den Anforderungen der Anwendung kann jedoch auch eine einfache Klasse ausreichen. In der Fallstudie wird beispielsweise in der News-App die Klasse NewsViewModel als Statusinhaber verwendet, um den UI-Status für den in diesem Abschnitt angezeigten Bildschirm zu generieren.
Es gibt viele Möglichkeiten, die Abhängigkeit zwischen der Benutzeroberfläche und dem zugehörigen Status-Producer zu modellieren. Da die Interaktion zwischen der Benutzeroberfläche und der zugehörigen ViewModel-Klasse jedoch weitgehend als Eingabe von Ereignissen und der daraus resultierenden Ausgabe von Status verstanden werden kann, lässt sich die Beziehung wie im folgenden Diagramm darstellen:
Das Muster, bei dem der Status nach unten und die Ereignisse nach oben fließen, wird als unidirektionaler Datenfluss (Unidirectional Data Flow, UDF) bezeichnet. Die Auswirkungen dieses Musters auf die App-Architektur sind folgende:
- Das ViewModel enthält und stellt den Status bereit, der von der Benutzeroberfläche verwendet werden soll. Der UI-Zustand sind Anwendungsdaten, die vom ViewModel transformiert werden.
- Die Benutzeroberfläche benachrichtigt das ViewModel über Nutzerereignisse.
- Das ViewModel verarbeitet die Nutzeraktionen und aktualisiert den Status.
- Der aktualisierte Status wird an die Benutzeroberfläche zurückgegeben, um gerendert zu werden.
- Das oben Genannte wird für jedes Ereignis wiederholt, das eine Statusänderung verursacht.
Für Navigationsziele oder ‑bildschirme ruft das ViewModel mit Repositories oder Use-Case-Klassen Daten ab und transformiert sie in den UI-Zustand. Dabei werden die Auswirkungen von Ereignissen berücksichtigt, die zu Änderungen des Zustands führen können. Die oben erwähnte Fallstudie enthält eine Liste von Artikeln mit jeweils einem Titel, einer Beschreibung, einer Quelle, einem Namen des Autors, einem Erscheinungsdatum und der Information, ob der Artikel mit einem Lesezeichen versehen wurde. Die Benutzeroberfläche für die einzelnen Artikel sieht so aus:
Wenn ein Nutzer einen Artikel mit einem Lesezeichen versehen möchte, ist das ein Beispiel für ein Ereignis, das Statusänderungen verursachen kann. Als State-Producer ist das ViewModel dafür verantwortlich, die gesamte Logik zu definieren, die zum Ausfüllen aller Felder im UI-Zustand und zum Verarbeiten der Ereignisse erforderlich ist, damit die Benutzeroberfläche vollständig gerendert werden kann.
In den folgenden Abschnitten werden die Ereignisse, die Statusänderungen verursachen, und die Verarbeitung mit UDFs genauer betrachtet.
Arten von Logik
Das Setzen eines Lesezeichens für einen Artikel ist ein Beispiel für Geschäftslogik, da es einen Mehrwert für Ihre App bietet. Weitere Informationen finden Sie auf der Seite Data Layer. Es gibt jedoch verschiedene Arten von Logik, die definiert werden müssen:
- Die Geschäftslogik ist die Implementierung von Produktanforderungen für App-Daten. Wie bereits erwähnt, ist ein Beispiel das Setzen eines Lesezeichens für einen Artikel in der Fallstudien-App. Die Geschäftslogik befindet sich in der Regel in den Domain- oder Datenschichten, aber niemals in der UI-Schicht.
- Die Logik für das UI-Verhalten oder UI-Logik beschreibt, wie Zustandsänderungen auf dem Bildschirm angezeigt werden. Beispiele hierfür sind das Abrufen des richtigen Texts, der auf dem Bildschirm angezeigt werden soll, mit Android
Resources, das Navigieren zu einem bestimmten Bildschirm, wenn der Nutzer auf eine Schaltfläche klickt, oder das Anzeigen einer Nutzermitteilung auf dem Bildschirm mit einem Toast oder einem Snackbar.
Die UI-Logik sollte in der UI und nicht im ViewModel enthalten sein, insbesondere wenn es sich um UI-Typen wie Context handelt.
Wenn die Benutzeroberfläche komplexer wird und Sie die UI-Logik an eine andere Klasse delegieren möchten, um die Testbarkeit und die Trennung von Belangen zu verbessern, können Sie eine einfache Klasse als State Holder erstellen. Einfache Klassen, die in der Benutzeroberfläche erstellt werden, können Android SDK-Abhängigkeiten haben, da sie dem Lebenszyklus der Benutzeroberfläche folgen. ViewModel-Objekte haben eine längere Lebensdauer.
Weitere Informationen zu State-Holdern und dazu, wie sie beim Erstellen der Benutzeroberfläche helfen, finden Sie im Leitfaden zu Jetpack Compose State.
Vorteile von UDF
Die UDF bildet den Zyklus der Statusgenerierung ab, wie in Abbildung 4 dargestellt. Außerdem werden die Orte, an denen Zustandsänderungen entstehen, transformiert und schließlich verwendet, voneinander getrennt. Durch diese Trennung kann die Benutzeroberfläche genau das tun, was ihr Name impliziert: Informationen anzeigen, indem sie Statusänderungen beobachtet, und Nutzerabsichten weiterleiten, indem sie diese Änderungen an das ViewModel übergibt.
Mit anderen Worten: UDFs ermöglichen Folgendes:
- Datenkonsistenz: Es gibt eine einzige Quelle für die Benutzeroberfläche.
- Testbarkeit: Die Quelle des Status ist isoliert und kann daher unabhängig von der Benutzeroberfläche getestet werden.
- Wartbarkeit: Die Änderung des Status folgt einem genau definierten Muster, bei dem Änderungen sowohl auf Nutzerereignisse als auch auf die Datenquellen zurückzuführen sind, aus denen Daten abgerufen werden.
UI-Status verfügbar machen
Nachdem Sie den UI-Status definiert und festgelegt haben, wie Sie die Erstellung dieses Status verwalten, besteht der nächste Schritt darin, den erstellten Status in der Benutzeroberfläche darzustellen.
Wenn Sie UDF verwenden, um die Erstellung des Status zu verwalten, kann der erstellte Status als Stream betrachtet werden. Das bedeutet, dass im Laufe der Zeit mehrere Versionen des Status erstellt werden. Stellen Sie den UI-Status in einem beobachtbaren Daten-Holder wie StateFlow bereit. So kann die Benutzeroberfläche auf Änderungen im Status reagieren, ohne dass Daten manuell direkt aus dem ViewModel abgerufen werden müssen. Das hat auch den Vorteil, dass immer die aktuelle Version des UI-Zustands im Cache gespeichert ist, was für die schnelle Wiederherstellung des Zustands nach Konfigurationsänderungen nützlich ist.
class NewsViewModel(...) : ViewModel() {
val uiState: NewsUiState = …
}
Eine Einführung in Kotlin-Flows finden Sie unter Kotlin-Flows unter Android.
Informationen zur Verwendung von StateFlow als beobachtbarer Daten-Holder finden Sie im Codelab Erweiterte Konzepte für Zustand und Side Effects in Jetpack Compose.
Wenn die in der Benutzeroberfläche angezeigten Daten relativ einfach sind, lohnt es sich oft, sie in einen UI-Zustandstyp einzuschließen, da so die Beziehung zwischen der Ausgabe des Zustandshalters und dem zugehörigen Bildschirm oder UI-Element verdeutlicht wird. Wenn das UI-Element komplexer wird, lässt sich die Definition des UI-Zustands ganz einfach erweitern, sodass Sie die zusätzlichen Informationen berücksichtigen können, die zum Rendern des UI-Elements erforderlich sind.
Eine gängige Methode zum Erstellen eines Streams von UiState ist das Bereitstellen einer mutableStateOf-Eigenschaft mit einem private set. Der Status bleibt im ViewModel änderbar, ist aber für die Benutzeroberfläche schreibgeschützt.
class NewsViewModel(...) : ViewModel() {
var uiState by mutableStateOf(NewsUiState())
private set
...
}
Das ViewModel kann dann Methoden bereitstellen, die den Status intern ändern und Updates für die Benutzeroberfläche veröffentlichen. Nehmen wir beispielsweise an, Sie müssen eine asynchrone Aktion ausführen.
Sie können eine Coroutine mit viewModelScope starten und den veränderlichen Status nach Abschluss aktualisieren.
class NewsViewModel(
private val repository: NewsRepository,
...
) : ViewModel() {
var uiState by mutableStateOf(NewsUiState())
private set
private var fetchJob: Job? = null
fun fetchArticles(category: String) {
fetchJob?.cancel()
fetchJob = viewModelScope.launch {
try {
val newsItems = repository.newsItemsForCategory(category)
uiState = uiState.copy(newsItems = newsItems)
} catch (ioe: IOException) {
// Handle the error and notify the UI when appropriate.
val messages = getMessagesFromThrowable(ioe)
uiState = uiState.copy(userMessages = messages)
}
}
}
}
Im vorherigen Beispiel versucht die Klasse NewsViewModel, Artikel für eine bestimmte Kategorie abzurufen, und spiegelt dann das Ergebnis des Versuchs – ob erfolgreich oder nicht – im UI-Status wider, sodass die Benutzeroberfläche entsprechend reagieren kann.
Weitere Informationen zur Fehlerbehandlung finden Sie im Abschnitt Fehler auf dem Bildschirm anzeigen.
Weitere Überlegungen
Zusätzlich zu den vorherigen Richtlinien sollten Sie beim Bereitstellen des UI-Zustands Folgendes beachten:
Verwenden Sie ein einzelnes UI-Zustandsobjekt, um zusammengehörige Zustände zu verarbeiten. Das führt zu weniger Inkonsistenzen und macht den Code leichter verständlich. Wenn Sie die Liste der Nachrichtenartikel und die Anzahl der Lesezeichen in zwei verschiedenen Streams bereitstellen, kann es passieren, dass einer aktualisiert wird und der andere nicht. Wenn Sie einen einzelnen Stream verwenden, werden beide Elemente auf dem neuesten Stand gehalten. Außerdem kann für einige Geschäftslogik eine Kombination von Quellen erforderlich sein. Beispielsweise müssen Sie möglicherweise eine Schaltfläche zum Setzen von Lesezeichen nur anzeigen, wenn der Nutzer angemeldet und Abonnent eines Premium-Nachrichtendienstes ist. So definieren Sie eine UI-Statusklasse:
data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf() ) val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremiumIn dieser Deklaration ist die Sichtbarkeit der Lesezeichenschaltfläche eine abgeleitete Eigenschaft von zwei anderen Eigenschaften. Je komplexer die Geschäftslogik wird, desto wichtiger ist es, eine einzelne
UiState-Klasse zu haben, in der alle Eigenschaften sofort verfügbar sind.UI-Status: Einzelner Stream oder mehrere Streams? Der wichtigste Grundsatz bei der Entscheidung, ob der UI-Status in einem einzelnen Stream oder in mehreren Streams bereitgestellt werden soll, ist die Beziehung zwischen den ausgegebenen Elementen. Die größten Vorteile einer Single-Stream-Exposition sind die Benutzerfreundlichkeit und die Datenkonsistenz: Nutzer von Status haben jederzeit Zugriff auf die neuesten Informationen. Es gibt jedoch Fälle, in denen separate Streams von Status aus dem ViewModel sinnvoll sein können:
Unabhängige Datentypen:Einige Status, die zum Rendern der Benutzeroberfläche erforderlich sind, sind möglicherweise völlig unabhängig voneinander. In solchen Fällen überwiegen die Kosten für die Zusammenfassung dieser unterschiedlichen Status möglicherweise die Vorteile, insbesondere wenn einer dieser Status häufiger aktualisiert wird als der andere.
UiState-Vergleich: Je mehr Felder einUiState-Objekt enthält, desto wahrscheinlicher ist es, dass der Stream aufgrund der Aktualisierung eines seiner Felder ausgegeben wird. Da UI-Elemente keinen Differenzierungsmechanismus haben, um zu erkennen, ob aufeinanderfolgende Emissionen unterschiedlich oder gleich sind, führt jede Emission zu einer Aktualisierung des UI-Elements. Das bedeutet, dass möglicherweise Gegenmaßnahmen mitFlow-API-Methoden wiedistinctUntilChanged()erforderlich sind.
Weitere Informationen zum Rendern und zum UI-Status finden Sie unter Lebenszyklus von Composables.
UI-Zustand verwenden
Wenn Sie den Stream von UiState-Objekten in der Benutzeroberfläche verwenden möchten, verwenden Sie den Terminaloperator für den Observable-Datentyp, den Sie verwenden. Verwenden Sie beispielsweise für Kotlin-Flows die Methode collect() oder ihre Varianten.
Wenn Sie beobachtbare Dateninhaber in der Benutzeroberfläche verwenden, müssen Sie den Lebenszyklus der Benutzeroberfläche berücksichtigen. Die UI soll den UI-Status nicht beobachten, wenn das Composable dem Nutzer nicht angezeigt wird. Weitere Informationen Wenn Sie Flows verwenden, sollten Sie Lifecycle-Probleme am besten mit dem entsprechenden Coroutine-Scope und der collectAsStateWithLifecycle API beheben:
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
Laufende Vorgänge anzeigen
Eine einfache Möglichkeit, Ladestatus in einer UiState-Klasse darzustellen, ist ein boolesches Feld:
data class NewsUiState(
val isFetchingArticles: Boolean = false,
...
)
Der Wert dieses Flags gibt an, ob in der Benutzeroberfläche eine Fortschrittsanzeige vorhanden ist.
@Composable
fun LatestNewsScreen(
modifier: Modifier = Modifier,
viewModel: NewsViewModel = viewModel()
) {
Box(modifier.fillMaxSize()) {
if (viewModel.uiState.isFetchingArticles) {
CircularProgressIndicator(Modifier.align(Alignment.Center))
}
// Add other UI elements. For example, the list.
}
}
Fehler auf dem Bildschirm anzeigen
Die Anzeige von Fehlern in der Benutzeroberfläche ähnelt der Anzeige von laufenden Vorgängen, da beide einfach durch boolesche Werte dargestellt werden können, die ihr Vorhandensein oder Fehlen angeben. Fehler können jedoch auch eine zugehörige Nachricht enthalten, die an den Nutzer zurückgegeben wird, oder eine zugehörige Aktion, mit der der fehlgeschlagene Vorgang noch einmal versucht wird. Während ein laufender Vorgang entweder geladen wird oder nicht, müssen Fehlerstatus möglicherweise mit Datenklassen modelliert werden, die die für den Kontext des Fehlers geeigneten Metadaten enthalten.
Sehen Sie sich das vorherige Beispiel an, in dem beim Abrufen von Artikeln ein Fortschrittsbalken angezeigt wurde. Wenn bei diesem Vorgang ein Fehler auftritt, sollten Sie dem Nutzer möglicherweise eine oder mehrere Meldungen anzeigen, in denen die Ursache des Fehlers erläutert wird.
data class Message(val id: Long, val message: String)
data class NewsUiState(
val userMessages: List<Message> = listOf(),
...
)
Sie können die Fehlermeldungen dann in Form von UI-Elementen wie Infoleisten für den Nutzer anzeigen. Weitere Informationen dazu, wie UI-Ereignisse erzeugt und genutzt werden, finden Sie unter UI-Ereignisse.
Threading und Nebenläufigkeit
Achten Sie darauf, dass alle Vorgänge, die in einem ViewModel ausgeführt werden, main-safe sind, d. h. sicher vom Hauptthread aus aufgerufen werden können. Die Daten- und Domänenebenen sind dafür verantwortlich, Arbeit auf einen anderen Thread zu verlagern.
Wenn ein ViewModel Vorgänge mit langer Ausführungszeit ausführt, ist es auch dafür verantwortlich, diese Logik in einen Hintergrundthread zu verschieben. Kotlin-Coroutinen sind eine gute Möglichkeit, gleichzeitige Vorgänge zu verwalten, und die Jetpack-Architekturkomponenten bieten integrierte Unterstützung dafür. Weitere Informationen zur Verwendung von Koroutinen in Android-Apps
Navigation
Änderungen in der App-Navigation werden oft durch ereignisähnliche Emissionen ausgelöst. Wenn sich beispielsweise ein Nutzer über eine SignInViewModel-Klasse anmeldet, kann für das UiState-Objekt das Feld isSignedIn auf true gesetzt sein. Verwende diese Trigger genauso wie die im vorherigen Abschnitt UI-Status verwenden beschriebenen, aber verschiebe die Implementierung der Verwendung auf die Navigationskomponente.
Weitere Informationen zur Navigation auf der Benutzeroberfläche findest du unter Navigation 3.
Paging
Die Paging-Bibliothek wird in der Benutzeroberfläche mit einem Typ namens PagingData verwendet. Da PagingData Elemente darstellt und enthält, die sich im Laufe der Zeit ändern können – es ist also kein unveränderlicher Typ –, sollten Sie es nicht in einem unveränderlichen UI-Zustand darstellen.
Stattdessen sollten Sie sie unabhängig vom ViewModel in einem eigenen Stream verfügbar machen.
Das folgende Beispiel zeigt die Compose API der Paging-Bibliothek:
@Composable fun MyScreen(flow: Flow<PagingData<String>>) { val lazyPagingItems = flow.collectAsLazyPagingItems() LazyColumn { items( lazyPagingItems.itemCount, key = lazyPagingItems.itemKey { it } ) { index -> val item = lazyPagingItems[index] Text("Item is $item") } } }
Animationen
Damit die Übergänge in der Navigation auf oberster Ebene reibungslos ablaufen, sollten Sie warten, bis auf dem zweiten Bildschirm Daten geladen wurden, bevor Sie die Animation starten.
Weitere Informationen zu Navigationsübergängen finden Sie unter Navigation 3 und Übergänge für gemeinsame Elemente in Compose.
Zusätzliche Ressourcen
Inhalte ansehen
Beispiele
Die folgenden Google-Beispiele veranschaulichen die Verwendung der UI-Ebene. Sehen Sie sich die Beispiele an, um zu sehen, wie die Richtlinien in der Praxis aussehen:
Empfehlungen für Sie
- Hinweis: Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Zustandsproduktion in der Benutzeroberfläche
- State-Holder und UI-Status {:#mad-arch}
- Leitfaden zur App-Architektur