Die Rolle der Benutzeroberfläche besteht darin, die Anwendungsdaten auf dem Bildschirm anzuzeigen. Die Benutzeroberfläche ist auch der primäre Punkt der Nutzerinteraktion. Wenn sich die Daten ändern, entweder aufgrund einer Nutzerinteraktion (z. B. Drücken einer Taste) oder einer externen Eingabe (z. B. einer Netzwerkantwort), wird die Benutzeroberfläche aktualisiert, um diese Änderungen widerzuspiegeln. Im Grunde ist die Benutzeroberfläche eine visuelle Darstellung des Anwendungsstatus, der von der Datenebene abgerufen wird.
Die Anwendungsdaten, die Sie von der Datenebene erhalten, haben jedoch in der Regel ein anderes Format als die Informationen, die Sie anzeigen müssen. Beispielsweise benötigen Sie möglicherweise nur einen Teil der Daten für die Benutzeroberfläche oder Sie müssen zwei verschiedene Datenquellen zusammenführen, um Informationen zu präsentieren, die für den Nutzer relevant sind. Unabhängig von der Logik, die Sie anwenden, müssen Sie der Benutzeroberfläche alle Informationen übergeben, die sie für das vollständige Rendern benötigt. Die UI-Ebene ist die Pipeline, die Änderungen an Anwendungsdaten in ein Format konvertiert, das die UI darstellen kann, und sie dann anzeigt.
Eine einfache Fallstudie
Angenommen, Sie haben eine App, die Nachrichtenartikel für Nutzer abruft. Die App hat einen Artikelbildschirm, auf dem Artikel zum Lesen angezeigt werden. Angemeldete Nutzer können außerdem Artikel, die ihnen besonders gefallen, als Lesezeichen speichern. Da es jederzeit viele Artikel geben kann, muss der Leser die Artikel nach Kategorie durchsuchen können. Zusammenfassend können Nutzer mit der App Folgendes tun:
- Verfügbare Artikel ansehen.
- Artikel nach Kategorie durchsuchen
- Melden Sie sich an und setzen Sie Lesezeichen für bestimmte Artikel.
- 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-Ebene lösen.
Architektur der UI-Ebene
Der Begriff UI bezieht sich auf UI-Elemente wie Container und zusammensetzbare Funktionen, die Daten anzeigen. Für die Erstellung von Android-Benutzeroberflächen wird das Toolkit Jetpack Compose empfohlen. Da die Rolle der Datenebene darin besteht, die App-Daten zu speichern, zu verwalten und Zugriff darauf zu gewähren, muss die UI-Ebene die folgenden Schritte ausführen:
- App-Daten nutzen und in Daten umwandeln, die die Benutzeroberfläche leicht rendern kann.
- UI-renderbare Daten verarbeiten und in UI-Elemente für die Präsentation für den Nutzer umwandeln.
- Nutzereingabeereignisse von diesen zusammengestellten UI-Elementen verarbeiten und ihre Auswirkungen bei Bedarf in den UI-Daten widerspiegeln.
- Wiederholen Sie die Schritte 1 bis 3 so oft wie nötig.
Im weiteren Verlauf 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
- Implementieren einer UI, die den beobachtbaren UI-Status nutzt
Der grundlegendste davon ist die Definition des UI-Status.
UI-Status definieren
In der zuvor beschriebenen Fallstudie zeigt die Benutzeroberfläche eine Liste von Artikeln sowie einige Metadaten für jeden Artikel an. Diese Informationen, die die App dem Nutzer präsentiert, sind der UI-Status.
Mit anderen Worten: Wenn die UI das ist, was der Nutzer sieht, ist der UI-Status das, was die App dem Nutzer anzeigen soll. Wie zwei Seiten derselben Medaille ist die UI die visuelle Darstellung des UI-Status. Alle Änderungen am UI-Status werden sofort in der UI widergespiegelt.
Betrachten wir die Fallstudie: Um die Anforderungen der Nachrichten-App zu erfüllen, können die Informationen, die zum vollständigen Rendern der Benutzeroberfläche erforderlich sind, in einer Datenklasse NewsUiState 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 Status und Jetpack Compose.
Unveränderlichkeit
Die Definition des UI-Status im vorherigen Beispiel ist unveränderlich. Der Hauptvorteil besteht darin, dass unveränderliche Objekte Garantien für den Status der Anwendung zu einem bestimmten Zeitpunkt bieten. So kann sich die UI auf ihre Hauptaufgabe konzentrieren: den Status lesen und die UI-Elemente entsprechend aktualisieren. Ändern Sie den UI-Status niemals direkt in der UI, es sei denn, die UI selbst ist die einzige Quelle ihrer Daten. Ein Verstoß gegen dieses Prinzip führt zu mehreren „Sources of Truth“ für dieselbe Information, was zu Dateninkonsistenzen und subtilen Fehlern führt.
Betrachten Sie beispielsweise die frühere Fallstudie.
Wenn das Flag bookmarked in einem NewsItemUiState-Objekt aus dem UI-Status in der Klasse Activity aktualisiert wird, konkurriert dieses Flag mit der Datenschicht als Quelle für den Lesezeichenstatus eines Artikels. Unveränderliche Datenklassen sind sehr nützlich, um diese Art von Inkonsistenz zu verhindern.
Namenskonventionen in diesem Leitfaden
In diesem Leitfaden werden UI-Zustandsklassen nach der Funktionalität des Bildschirms oder des Teils des Bildschirms benannt, den sie beschreiben. Die Konvention lautet so:
Funktionalität + UiState.
Der Status eines Bildschirms, auf dem Nachrichten angezeigt werden, könnte beispielsweise NewsUiState heißen und der Status eines Nachrichtenelements in einer Liste von Nachrichtenelementen könnte NewsItemUiState sein.
Zustand mit unidirektionalem Datenfluss verwalten
Im vorherigen Abschnitt wurde dargelegt, dass der UI-Status ein unveränderlicher Snapshot der Details ist, die für das Rendern der UI erforderlich sind. Aufgrund der dynamischen Natur von Daten in Apps kann sich der Status jedoch im Laufe der Zeit ändern. Dies kann auf Nutzerinteraktionen oder andere Ereignisse zurückzuführen sein, die die zugrunde liegenden Daten ändern, die zum Ausfüllen der App verwendet werden.
Diese Interaktionen können von einem Mediator profitieren, der sie verarbeitet, die Logik definiert, die auf jedes Ereignis angewendet werden soll, und die zugrunde liegenden Datenquellen transformiert, um den UI-Status zu erstellen. Diese Interaktionen und ihre Logik können zwar in der Benutzeroberfläche selbst untergebracht werden, dies kann jedoch 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 einzige Aufgabe der UI darin bestehen, den UI-Status zu nutzen und anzuzeigen.
In diesem Abschnitt wird der unidirektionale Datenfluss (UDF) erläutert, ein Architekturmuster, das diese sinnvolle Trennung der Verantwortlichkeiten unterstützt.
State Holder
State Holder sind die Klassen, die für die Erstellung des UI-Status und für die Logik verantwortlich sind, die zum Erstellen dieses Status erforderlich ist. State Holder gibt es in verschiedenen Größen, je nach Umfang der entsprechenden UI-Elemente, die sie verwalten. Das reicht von einem einzelnen Widget wie einer unteren App-Leiste bis hin zu einem ganzen Bildschirm oder einem Navigationsziel.
Im letzteren Fall ist die typische Implementierung eine Instanz eines ViewModel, obwohl je nach den Anforderungen der Anwendung eine einfache Klasse ausreichen kann. Die Nachrichten-App aus der Fallstudie verwendet beispielsweise eine NewsViewModel-Klasse als State Holder, um den UI-Status für den in diesem Abschnitt angezeigten Bildschirm zu erzeugen.
Es gibt viele Möglichkeiten, die gegenseitige Abhängigkeit zwischen der Benutzeroberfläche und dem State Producer zu modellieren. Da die Interaktion zwischen der UI und ihrer ViewModel-Klasse jedoch weitgehend als Ereignis-Eingabe und die daraus resultierende Status-Ausgabe 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 (UDF) bezeichnet. Die Auswirkungen dieses Musters auf die Anwendungsarchitektur sind folgende:
- Das ViewModel enthält den Status, der von der UI verwendet werden soll, und stellt ihn bereit. Der UI-Zustand sind Anwendungsdaten, die vom ViewModel transformiert wurden.
- Die UI 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.
- Die oben genannten Schritte werden für jedes Ereignis wiederholt, das eine Statusänderung verursacht.
Bei Navigationszielen oder Bildschirmen arbeitet das ViewModel mit Repositories oder Anwendungsfallklassen, um Daten abzurufen und in den UI-Status umzuwandeln. Dabei werden die Auswirkungen von Ereignissen berücksichtigt, die zu Änderungen des Status führen können. Die zuvor erwähnte Fallstudie enthält eine Liste von Artikeln, die jeweils einen Titel, eine Beschreibung, eine Quelle, einen Autorennamen, ein Erscheinungsdatum und die Angabe, ob sie mit einem Lesezeichen versehen wurden, enthalten. Die Benutzeroberfläche für die einzelnen Artikel sieht so aus:
Ein Nutzer, der ein Lesezeichen für einen Artikel setzt, ist ein Beispiel für ein Ereignis, das Statusänderungen verursachen kann. Als Zustandsersteller ist das ViewModel dafür verantwortlich, die gesamte Logik zu definieren, die erforderlich ist, um alle Felder im UI-Zustand zu füllen und die Ereignisse zu verarbeiten, die für das vollständige Rendern der UI erforderlich sind.
In den folgenden Abschnitten werden die Ereignisse, die Statusänderungen verursachen, und die Verarbeitung mit UDFs genauer beschrieben.
Logiktypen
Das Setzen eines Lesezeichens für einen Artikel ist ein Beispiel für Geschäftslogik, da es Ihrer App einen Mehrwert verleiht. Weitere Informationen hierzu finden Sie auf der Seite Datenebene. Es gibt jedoch verschiedene Arten von Logik, die definiert werden müssen:
- Geschäftslogik ist die Implementierung von Produktanforderungen für App-Daten. Wie bereits erwähnt, ist ein Beispiel dafür das Setzen eines Lesezeichens für einen Artikel in der Fallstudien-App. Die Geschäftslogik befindet sich in der Regel in der Domain- oder Datenebene, aber nie in der UI-Ebene.
- Die Logik des UI-Verhaltens oder die UI-Logik bestimmt, wie Zustandsänderungen auf dem Bildschirm angezeigt werden. Beispiele hierfür sind das Abrufen des richtigen Texts für die Anzeige auf dem Bildschirm mit Android-
Resources, das Aufrufen eines bestimmten Bildschirms, wenn der Nutzer auf eine Schaltfläche klickt, oder das Anzeigen einer Nutzernachricht auf dem Bildschirm mit einem Toast oder einer Snackbar.
Behalten Sie die UI-Logik in der UI und nicht im ViewModel, 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 Zuständigkeiten zu verbessern, können Sie eine einfache Klasse als State Holder erstellen. Einfache Klassen, die in der UI erstellt werden, können Android SDK-Abhängigkeiten übernehmen, da sie dem Lebenszyklus der UI folgen. ViewModel-Objekte haben eine längere Lebensdauer.
Weitere Informationen zu State Holdern und dazu, wie sie in den Kontext der UI-Erstellung passen, finden Sie im Leitfaden zu Jetpack Compose State.
Vorteile von UDFs
Die UDF modelliert den Zyklus der Statusproduktion wie in Abbildung 4 dargestellt. Außerdem werden der Ort, an dem Zustandsänderungen entstehen, der Ort, an dem sie transformiert werden, und der Ort, an dem sie schließlich verarbeitet werden, voneinander getrennt. Durch diese Trennung kann die UI genau das tun, was ihr Name impliziert: Informationen anzeigen, indem sie Statusänderungen beobachtet, und die Nutzerabsicht weitergeben, indem sie diese Änderungen an das ViewModel weitergibt.
Mit anderen Worten: UDF ermöglicht Folgendes:
- Datenkonsistenz: Es gibt eine einzige verlässliche Quelle für die Benutzeroberfläche.
- Testbarkeit: Die Quelle des Status ist isoliert und kann daher unabhängig von der UI getestet werden.
- Wartbarkeit: Die Statusänderung folgt einem klar definierten Muster, bei dem Änderungen sowohl das Ergebnis von Nutzerereignissen als auch der Datenquellen sind, aus denen sie stammen.
UI-Status verfügbar machen
Nachdem Sie den UI-Status definiert und festgelegt haben, wie Sie die Produktion dieses Status verwalten, besteht der nächste Schritt darin, den produzierten Status in der UI darzustellen.
Wenn Sie eine UDF verwenden, um die Produktion des Status zu verwalten, können Sie den erzeugten Status als Stream betrachten. Mit anderen Worten: Im Laufe der Zeit werden mehrere Versionen des Status erzeugt. Stellen Sie den UI-Status in einem beobachtbaren Datenhalter wie StateFlow bereit. So kann die UI auf alle Änderungen im Status reagieren, ohne dass Daten manuell direkt aus dem ViewModel abgerufen werden müssen. Dies hat auch den Vorteil, dass immer die aktuelle Version des UI-Status im Cache gespeichert ist, was für eine schnelle Wiederherstellung des Status nach Konfigurationsänderungen nützlich ist.
class NewsViewModel( // ... ) : ViewModel() { val uiState: NewsUiState = /* ... */ }
Eine Einführung in Kotlin-Abläufe finden Sie unter Kotlin-Abläufe unter Android.
Informationen zur Verwendung von StateFlow als beobachtbaren Datenhalter finden Sie im Codelab Erweiterte Konzepte für Zustand und Side Effects in Jetpack Compose.
Wenn die in der UI angezeigten Daten relativ einfach sind, lohnt es sich oft, die Daten in einen UI-Zustandstyp zu verpacken, da so die Beziehung zwischen der Ausgabe des State Holders und dem zugehörigen Bildschirm oder UI-Element vermittelt wird. Wenn das UI-Element komplexer wird, lässt sich die Definition des UI-Status ganz einfach erweitern, sodass Sie die zusätzlichen Informationen, die zum Rendern des UI-Elements erforderlich sind, berücksichtigen können.
Eine gängige Methode zum Erstellen eines Streams von UiState besteht darin, eine mutableStateOf-Property mit einem private set bereitzustellen, wobei der Status im ViewModel änderbar, aber für die UI schreibgeschützt bleibt.
class NewsViewModel( // ... ) : ViewModel() { var uiState by mutableStateOf(NewsUiState()) private set // ... }
Das ViewModel kann dann Methoden bereitstellen, die den Status intern ändern und Aktualisierungen für die UI veröffentlichen. Nehmen wir zum Beispiel an, Sie müssen eine asynchrone Aktion ausführen.
Sie können eine Koroutine mit viewModelScope starten und dann den änderbaren 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 UI entsprechend reagieren kann.
Weitere Informationen zur Fehlerbehandlung finden Sie im Abschnitt Fehler auf dem Bildschirm anzeigen.
Weitere Überlegungen
Zusätzlich zu den vorherigen Hinweisen sollten Sie beim Offenlegen des UI-Status Folgendes beachten:
Verwenden Sie ein einzelnes UI-Zustandsobjekt, um Zustände zu verarbeiten, die miteinander in Beziehung stehen. Dies führt zu weniger Inkonsistenzen und macht den Code leichter verständlich. Wenn Sie die Liste der Nachrichten und die Anzahl der Lesezeichen in zwei verschiedenen Streams verfügbar machen, kann es passieren, dass einer aktualisiert wurde und der andere nicht. Wenn Sie einen einzelnen Stream verwenden, werden beide Elemente auf dem neuesten Stand gehalten. Darüber hinaus kann für eine Geschäftslogik eine Kombination von Quellen erforderlich sein. Beispielsweise kann es sein, dass Sie eine Lesezeichen-Schaltfläche nur dann anzeigen müssen, wenn der Nutzer angemeldet und Abonnent eines Premium-Nachrichtendienstes ist. Eine UI-Zustandsklasse können Sie so definieren:
data class NewsUiState( val isSignedIn: Boolean = false, val isPremium: Boolean = false, val newsItems: List<NewsItemUiState> = listOf() ) val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium
In dieser Deklaration ist die Sichtbarkeit der Lesezeichen-Schaltfläche ein abgeleitetes Attribut von zwei anderen Attributen. Wenn die Geschäftslogik komplexer wird, ist es immer wichtiger, eine einzelne
UiState-Klasse zu haben, in der alle Attribute sofort verfügbar sind.UI-Zustände: einzelner Stream oder mehrere Streams? Das wichtigste Leitprinzip für die Entscheidung, ob der UI-Status in einem einzelnen Stream oder in mehreren Streams verfügbar gemacht werden soll, ist die Beziehung zwischen den ausgegebenen Elementen. Die größten Vorteile der Bereitstellung über einen einzigen Stream sind die einfache Handhabung und die Datenkonsistenz: Nutzer des Status haben immer die neuesten Informationen zur Verfügung. Es gibt jedoch Fälle, in denen separate Statusstreams aus dem ViewModel sinnvoll sein können:
Nicht zusammenhängende Datentypen:Einige Zustände, die zum Rendern der UI erforderlich sind, können völlig unabhängig voneinander sein. In solchen Fällen können die Kosten für die Bündelung dieser unterschiedlichen Zustände die Vorteile überwiegen, insbesondere wenn einer dieser Zustände häufiger aktualisiert wird als der andere.
UiState-Diffing: 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 Diffing-Mechanismus 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 Maßnahmen mitFlow-API-Methoden wiedistinctUntilChanged()erforderlich sind.
Weitere Informationen zum Rendern und zum UI-Status finden Sie unter Lebenszyklus von Composables.
UI-Zustand nutzen
Wenn Sie den Stream von UiState-Objekten in der UI nutzen möchten, verwenden Sie den Terminaloperator für den verwendeten beobachtbaren Datentyp. Verwenden Sie für Kotlin-Abläufe beispielsweise die Methode collect() oder ihre Varianten.
Wenn Sie beobachtbare Datenhalter in der Benutzeroberfläche verwenden, müssen Sie den Lebenszyklus der Benutzeroberfläche berücksichtigen. Die UI sollte den UI-Status nicht beobachten, wenn die zusammensetzbare Funktion dem Nutzer nicht angezeigt wird. Weitere Informationen zu diesem Thema finden Sie in diesem Blogpost. Wenn Sie Abläufe verwenden, sollten Sie Lebenszyklusprobleme mit dem entsprechenden Koroutinenbereich und der collectAsStateWithLifecycle API behandeln:
@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 oder nicht.
@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
Das Anzeigen von Fehlern in der Benutzeroberfläche ähnelt dem Anzeigen von laufenden Vorgängen, da beide leicht 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 weitergeleitet wird, oder eine zugehörige Aktion, mit der der fehlgeschlagene Vorgang wiederholt wird. Daher müssen Fehlerstatus möglicherweise mit Datenklassen modelliert werden, die die für den Kontext des Fehlers geeigneten Metadaten enthalten, während ein laufender Vorgang entweder geladen oder nicht geladen wird.
Sehen Sie sich das vorherige Beispiel an, in dem beim Abrufen von Artikeln eine Fortschrittsanzeige eingeblendet wurde. Wenn dieser Vorgang zu einem Fehler führt, können Sie dem Nutzer eine oder mehrere Meldungen anzeigen, in denen detailliert beschrieben wird, was schiefgelaufen ist.
data class Message(val id: Long, val message: String) data class NewsUiState( val userMessages: List<Message> = listOf(), // ... )
Anschließend können Sie dem Nutzer die Fehlermeldungen in Form von UI-Elementen wie Snackbars präsentieren. Weitere Informationen dazu, wie UI-Ereignisse erstellt und genutzt werden, finden Sie unter UI-Ereignisse.
Threading und Nebenläufigkeit
Achte darauf, dass alle in einem ViewModel ausgeführten Aufgaben hauptthreadsicher sind, also sicher vom Hauptthread aufgerufen werden können. Die Daten- und Domainebenen sind dafür verantwortlich, die Arbeit in einen anderen Thread zu verschieben.
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-Koroutinen sind eine hervorragende Möglichkeit, gleichzeitige Vorgänge zu verwalten. Die Jetpack-Architekturkomponenten bieten integrierte Unterstützung dafür. Weitere Informationen zur Verwendung von Koroutinen in Android-Apps finden Sie unter Kotlin-Koroutinen unter Android.
Navigation
Änderungen an der App-Navigation werden häufig durch ereignisähnliche Emissionen ausgelöst. Nachdem sich beispielsweise eine SignInViewModel-Klasse angemeldet hat, kann für die UiState das Feld isSignedIn auf true festgelegt sein. Solche Trigger werden wie die im vorherigen Abschnitt UI-Status nutzen beschriebenen Trigger genutzt. Die Implementierung der Nutzung wird jedoch an die Navigationskomponente delegiert.
Weitere Informationen zur Navigation auf der Benutzeroberfläche finden Sie unter Navigation 3.
Paging
Die Paging-Bibliothek wird in der Benutzeroberfläche mit dem Typ PagingData verwendet. Da PagingData Elemente darstellt und enthält, die sich im Laufe der Zeit ändern können, also kein unveränderlicher Typ ist, sollte es nicht in einem unveränderlichen UI-Zustand dargestellt werden.
Stattdessen solltest du ihn 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 bei der Navigation auf oberster Ebene reibungslos funktionieren, solltest du warten, bis die Daten auf dem zweiten Bildschirm geladen sind, bevor du die Animation startest.
Weitere Informationen zu Navigationsübergängen finden Sie unter Navigation 3 und Übergänge mit gemeinsam genutzten Elementen in Compose.
Zusätzliche Ressourcen
Inhalte ansehen
Beispiele
Die folgenden Google-Beispiele veranschaulichen die Verwendung der UI-Ebene. Sehen Sie sich die folgenden Beispiele an, um diese Anleitung in der Praxis zu sehen:
Empfehlungen für Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- Zustandsproduktion in der UI
- State Holder und UI-Status {:#mad-arch}
- Leitfaden zur App-Architektur