Statusinhaber und UI-Status

Im UI-Ebenen-Leitfaden wird der unidirektionale Datenfluss (UDF) als Möglichkeit zum Erstellen und Verwalten des UI-Status für die UI-Ebene erläutert.

Die Daten fließen unidirektional von der Datenschicht zur Benutzeroberfläche.
Abbildung 1: Unidirektionaler Datenfluss

Außerdem werden die Vorteile der Delegation der UDF-Verwaltung an eine spezielle Klasse, den sogenannten Staatsinhaber, hervorgehoben. Sie können einen Statusinhaber entweder mit einer ViewModel- oder einer einfachen Klasse implementieren. In diesem Dokument werden Inhaber von Bundesstaaten und ihre Rolle auf der UI-Ebene genauer beschrieben.

Am Ende dieses Dokuments sollten Sie wissen, wie Sie den Anwendungsstatus auf der UI-Ebene verwalten, d. h. mit der Produktionspipeline des UI-Status. Sie sollten Folgendes verstehen und wissen:

  • Informieren Sie sich über die Arten von UI-Status, die auf der UI-Ebene vorhanden sind.
  • Informieren Sie sich über die Arten von Logik, die auf diese UI-Zustände auf der UI-Ebene angewendet werden.
  • Machen Sie sich mit der Auswahl der geeigneten Implementierung eines Statusinhabers vertraut, z. B. einer ViewModel oder einer einfachen Klasse.

Elemente der Produktionspipeline für den UI-Status

Der UI-Status und die Logik, die ihn erzeugt, definieren die UI-Ebene.

UI-Status

Der UI-Status ist die Eigenschaft, die die UI beschreibt. Es gibt zwei Arten von UI-Status:

  • Der Bildschirm-UI-Status gibt an, was auf dem Bildschirm angezeigt werden muss. Eine NewsUiState-Klasse kann beispielsweise die Nachrichtenartikel und andere Informationen enthalten, die zum Rendern der UI erforderlich sind. Dieser Status ist normalerweise mit anderen Ebenen der Hierarchie verbunden, da er Anwendungsdaten enthält.
  • Der UI-Elementstatus bezieht sich auf Eigenschaften, die für UI-Elemente unveränderlich sind und die Darstellung beeinflussen. Ein UI-Element kann ein- oder ausgeblendet sein und eine bestimmte Schriftart, Schriftgröße oder Schriftfarbe haben. In Android-Ansichten verwaltet die Ansicht diesen Status selbst, da er inhärent zustandsorientiert ist und Methoden zur Änderung oder Abfrage seines Status zur Verfügung stellt. Ein Beispiel dafür sind die Methoden get und set der Klasse TextView für ihren Text. In Jetpack Composer liegt der Status außerhalb der zusammensetzbaren Funktion. Sie können ihn sogar aus der unmittelbaren Nähe der zusammensetzbaren Funktion in die aufrufende zusammensetzbare Funktion oder einen Zustandsinhaber heben. Ein Beispiel hierfür ist ScaffoldState für die zusammensetzbare Funktion Scaffold.

Logik

Der UI-Status ist keine statische Eigenschaft, da sich der UI-Status im Laufe der Zeit aufgrund von Anwendungsdaten und Nutzerereignissen ändert. Die Logik bestimmt die Details der Änderung, einschließlich der Teile des UI-Status, der geändert wurde, warum und wann er geändert werden sollte.

Logik erzeugt UI-Status
Abbildung 2: Logik als Ersteller des UI-Zustands

Die Logik in einer Anwendung kann entweder Geschäftslogik oder UI-Logik sein:

  • Die Geschäftslogik ist die Implementierung von Produktanforderungen für App-Daten. Ein Beispiel dafür wäre, einen Artikel in einer Newsreader-App als Lesezeichen zu speichern, wenn der Nutzer auf die Schaltfläche tippt. Diese Logik zum Speichern eines Lesezeichens in einer Datei oder Datenbank wird normalerweise in den Domain- oder Datenschichten platziert. Der Zustandsinhaber delegiert diese Logik in der Regel an diese Ebenen, indem er die von ihnen bereitgestellten Methoden aufruft.
  • UI-Logik bezieht sich darauf, wie der UI-Status auf dem Bildschirm angezeigt wird. Beispielsweise können Sie den richtigen Suchleistenhinweis abrufen, wenn der Nutzer eine Kategorie ausgewählt hat, zu einem bestimmten Element in einer Liste scrollt, oder die Navigationslogik zu einem bestimmten Bildschirm, wenn der Nutzer auf eine Schaltfläche klickt.

Android-Lebenszyklus und die Arten von UI-Status und -Logik

Die UI-Ebene besteht aus zwei Teilen: der eine ist abhängig und der andere unabhängig vom UI-Lebenszyklus. Durch diese Trennung werden die Datenquellen bestimmt, die für die einzelnen Teile verfügbar sind. Daher sind verschiedene Arten von UI-Zustand und Logik erforderlich.

  • UI-Lebenszyklus unabhängig: Dieser Teil der UI-Ebene befasst sich mit den Datenebenen der Anwendung (Daten- oder Domainebenen) und wird durch die Geschäftslogik definiert. Der Lebenszyklus, Konfigurationsänderungen und die Neuerstellung von Activity in der UI können sich darauf auswirken, ob die Produktionspipeline für den UI-Status aktiv ist. Die Gültigkeit der erzeugten Daten bleibt davon jedoch unberührt.
  • UI-Lebenszyklus abhängig: Dieser Teil der UI-Ebene befasst sich mit der UI-Logik und wird direkt von Lebenszyklus- oder Konfigurationsänderungen beeinflusst. Diese Änderungen wirken sich direkt auf die Gültigkeit der darin gelesenen Datenquellen aus. Daher kann sich ihr Status nur ändern, wenn ihr Lebenszyklus aktiv ist. Beispiele dafür sind Laufzeitberechtigungen und das Abrufen von konfigurationsabhängigen Ressourcen wie lokalisierten Strings.

Das kann mit der folgenden Tabelle zusammengefasst werden:

Unabhängig vom UI-Lebenszyklus Vom UI-Lebenszyklus abhängig
Geschäftslogik UI-Logik
Status der Benutzeroberfläche des Bildschirms

Produktionspipeline für den UI-Status

Die Produktionspipeline für den UI-Status bezieht sich auf die Schritte, die zum Erstellen des UI-Status ausgeführt werden. Diese Schritte umfassen die Anwendung der zuvor definierten Logiktypen und hängen vollständig von den Anforderungen Ihrer UI ab. Einige UIs können sowohl von unabhängigen Teilen des UI-Lebenszyklus als auch von von dem UI-Lebenszyklus abhängigen Teilen der Pipeline profitieren.

Das heißt, die folgenden Permutationen der UI-Ebenen-Pipeline sind gültig:

  • Der UI-Status, der von der UI selbst erstellt und verwaltet wird. Ein einfacher, wiederverwendbarer Basiszähler:

    @Composable
    fun Counter() {
        // The UI state is managed by the UI itself
        var count by remember { mutableStateOf(0) }
        Row {
            Button(onClick = { ++count }) {
                Text(text = "Increment")
            }
            Button(onClick = { --count }) {
                Text(text = "Decrement")
            }
        }
    }
    
  • UI-Logik → UI. Beispiel: Ein- oder Ausblenden einer Schaltfläche, mit der Nutzer an den Anfang einer Liste springen können.

    @Composable
    fun ContactsList(contacts: List<Contact>) {
        val listState = rememberLazyListState()
        val isAtTopOfList by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex < 3
            }
        }
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Show or hide the button (UI logic) based on the list scroll position
        AnimatedVisibility(visible = !isAtTopOfList) {
            ScrollToTopButton()
        }
    }
    
  • Geschäftslogik → Benutzeroberfläche. Ein UI-Element, das das Foto des aktuellen Nutzers auf dem Bildschirm anzeigt.

    @Composable
    fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
        // Call on the UserAvatar Composable to display the photo
        UserAvatar(picture = uiState.profilePicture)
    }
    
  • Geschäftslogik → UI-Logik → UI. Ein UI-Element, das scrollt, um die richtigen Informationen für einen bestimmten UI-Zustand auf dem Bildschirm anzuzeigen.

    @Composable
    fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
        val contacts = uiState.contacts
        val deepLinkedContact = uiState.deepLinkedContact
    
        val listState = rememberLazyListState()
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Perform UI logic that depends on information from business logic
        if (deepLinkedContact != null && contacts.isNotEmpty()) {
            LaunchedEffect(listState, deepLinkedContact, contacts) {
                val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact)
                if (deepLinkedContactIndex >= 0) {
                  // Scroll to deep linked item
                  listState.animateScrollToItem(deepLinkedContactIndex)
                }
            }
        }
    }
    

Wenn beide Arten von Logik auf die Produktionspipeline für den UI-Status angewendet werden, muss die Geschäftslogik immer vor der UI-Logik angewendet werden. Der Versuch, Geschäftslogik nach der UI-Logik anzuwenden, würde implizieren, dass die Geschäftslogik von der UI-Logik abhängt. In den folgenden Abschnitten erfahren Sie, warum dies ein Problem ist. Die verschiedenen Logiktypen und ihre Statusinhaber werden ausführlich beschrieben.

Daten fließen von der Datenebene zur Benutzeroberfläche.
Abbildung 3: Anwendung der Logik auf der UI-Ebene

Inhaber von Bundesstaaten und ihre Verantwortlichkeiten

Die Verantwortung eines Zustandsinhabers besteht darin, den Status zu speichern, damit die App ihn lesen kann. Wenn Logik erforderlich ist, fungiert sie als Vermittler und bietet Zugriff auf die Datenquellen, auf denen die erforderliche Logik gehostet wird. Auf diese Weise delegiert der Zustandsinhaber die Logik an die entsprechende Datenquelle.

Dies hat folgende Vorteile:

  • Einfache Benutzeroberflächen: Die Benutzeroberfläche bindet nur ihren Status.
  • Verwaltbarkeit: Die im Statusinhaber definierte Logik kann iteriert werden, ohne die UI selbst zu ändern.
  • Testbarkeit: Die UI und ihre Statusproduktionslogik können unabhängig getestet werden.
  • Lesbarkeit: Die Leser des Codes können deutliche Unterschiede zwischen dem UI-Darstellungscode und dem UI-Status-Produktionscode erkennen.

Unabhängig von Größe oder Umfang hat jedes UI-Element eine 1:1-Beziehung zum entsprechenden Statusinhaber. Darüber hinaus muss ein Zustandsinhaber in der Lage sein, jede Nutzeraktion, die zu einer Änderung des UI-Status führen könnte, zu akzeptieren und zu verarbeiten, und die darauffolgende Statusänderung erzeugen können.

Arten von Staatsinhabern

Ähnlich wie bei UI-Status und -Logik gibt es auf der UI-Ebene zwei Arten von Statusinhabern, die durch ihre Beziehung zum UI-Lebenszyklus definiert werden:

  • Der Inhaber der Geschäftslogik.
  • Der Inhaber des UI-Logikstatus.

In den folgenden Abschnitten werden die Typen von Statusinhabern genauer betrachtet, beginnend mit dem Inhaber der Geschäftslogik.

Geschäftslogik und ihr Statusinhaber

Inhaber der Geschäftslogik verarbeiten Nutzerereignisse und transformieren Daten aus den Daten- oder Domainebenen in den Bildschirm-UI-Status. Statusinhaber, die Geschäftslogik verwenden, sollten folgende Attribute haben, um unter Berücksichtigung des Android-Lebenszyklus und der App-Konfigurationsänderungen eine optimale Nutzererfahrung zu bieten:

Attribut Details
Erzeugt UI-Status Inhaber der Geschäftslogik sind für die Erstellung des UI-Status für ihre UIs verantwortlich. Dieser UI-Status ist häufig das Ergebnis der Verarbeitung von Nutzerereignissen und dem Lesen von Daten aus der Domain und den Datenschichten.
Erhalten durch Freizeitaktivitäten Inhaber von Geschäftslogikstatus behalten ihre Status- und Statusverarbeitungspipelines über die Activity-Neuerstellung hinweg bei und bieten so eine nahtlose Nutzererfahrung. In Fällen, in denen der Zustandsinhaber nicht beibehalten werden kann und neu erstellt wird (normalerweise nach dem Prozess Tod), muss der Statusinhaber in der Lage sein, seinen letzten Status einfach wiederherzustellen, um eine konsistente Nutzererfahrung zu gewährleisten.
Hat einen langlebigen Zustand Inhaber von Geschäftslogikstatus werden häufig verwendet, um den Status für Navigationsziele zu verwalten. Daher behalten sie häufig ihren Status über alle Navigationsänderungen hinweg bei, bis sie aus dem Navigationsdiagramm entfernt werden.
Sie ist für die zugehörige Benutzeroberfläche eindeutig und nicht wiederverwendbar. Inhaber von Geschäftslogikstatus generieren in der Regel einen Status für eine bestimmte App-Funktion, z. B. eine TaskEditViewModel oder eine TaskListViewModel. Daher gilt sie immer nur für diese App-Funktion. Derselbe Bundesstaat kann diese App-Funktionen für verschiedene Formfaktoren unterstützen. Beispielsweise kann in Smartphone-, TV- und Tablet-Versionen der App derselbe Inhaber der Geschäftslogik verwendet werden.

Nehmen wir als Beispiel das Navigationsziel für den Autor in der App Now in Android:

Die App „Now in Android“ zeigt, wie ein Navigationsziel, das eine wichtige App-Funktion darstellt, einen eigenen eindeutigen Status in der Geschäftslogik haben sollte.
Abbildung 4: Die App „Now in Android“

AuthorViewModel dient als Inhaber der Geschäftslogik und erzeugt in diesem Fall den UI-Status:

@HiltViewModel
class AuthorViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    private val authorsRepository: AuthorsRepository,
    newsRepository: NewsRepository
) : ViewModel() {

    val uiState: StateFlow<AuthorScreenUiState> = …

    // Business logic
    fun followAuthor(followed: Boolean) {
      …
    }
}

Beachten Sie, dass AuthorViewModel die zuvor beschriebenen Attribute hat:

Attribut Details
produziert AuthorScreenUiState AuthorViewModel liest Daten aus AuthorsRepository und NewsRepository und verwendet diese Daten, um AuthorScreenUiState zu erstellen. Außerdem wird Geschäftslogik angewendet, wenn der Nutzer Author durch Delegieren an die AuthorsRepository folgen oder nicht mehr folgen möchte.
Hat Zugriff auf die Datenschicht Eine Instanz von AuthorsRepository und NewsRepository wird in ihrem Konstruktor an die Instanz übergeben, damit sie die Geschäftslogik für das folgende Ereignis implementieren kann: Author.
Überlebt Activity Freizeit Da er mit einem ViewModel implementiert ist, wird er über eine schnelle Activity-Neuerstellung hinweg beibehalten. Wenn ein Prozess abgebrochen wird, kann das Objekt SavedStateHandle ausgelesen werden, um die Mindestmenge an Informationen bereitzustellen, die zum Wiederherstellen des UI-Status aus der Datenschicht erforderlich sind.
Besitzt langlebigen Zustand ViewModel bezieht sich auf das Navigationsdiagramm. Solange das Ziel des Autors nicht aus dem Navigationsdiagramm entfernt wird, bleibt der Status der Benutzeroberfläche im uiState-StateFlow im Arbeitsspeicher. Die Verwendung von StateFlow bietet außerdem den Vorteil, dass die Anwendung der Geschäftslogik, die den Zustand erzeugt, faul ist, da der Zustand nur erzeugt wird, wenn es einen Collector des UI-Status gibt.
Sie ist einzigartig für die zugehörige Benutzeroberfläche. Das AuthorViewModel gilt nur für das Navigationsziel des Verfassers und kann nirgendwo sonst wiederverwendet werden. Wenn eine Geschäftslogik vorhanden ist, die über Navigationsziele hinweg wiederverwendet wird, muss diese Geschäftslogik in eine Komponente auf Daten- oder Domainebene gekapselt werden.

ViewModel als Inhaber der Geschäftslogik

Aufgrund der Vorteile von ViewModels in der Android-Entwicklung eignen sie sich für den Zugriff auf die Geschäftslogik und die Vorbereitung der Anwendungsdaten für die Bildschirmpräsentation. Zu diesen Vorteilen gehört Folgendes:

  • Von ViewModels ausgelöste Vorgänge bleiben über Konfigurationsänderungen erhalten.
  • Einbindung in Navigation:
    • Navigation speichert ViewModels im Cache, während sich der Bildschirm im Back Stack befindet. Dies ist wichtig, damit die zuvor geladenen Daten sofort verfügbar sind, wenn Sie zum Ziel zurückkehren. Dies ist mit einem Statusinhaber, der dem Lebenszyklus des zusammensetzbaren Bildschirms folgt, schwieriger.
    • ViewModel wird auch gelöscht, wenn das Ziel vom Back-Stack getrennt wird. Dadurch wird sichergestellt, dass Ihr Zustand automatisch bereinigt wird. Dies unterscheidet sich vom Monitoring von zusammensetzbaren Funktionen, die aus verschiedenen Gründen auftreten können, z. B. wenn ein neuer Bildschirm aufgerufen wird, eine Konfigurationsänderung erforderlich ist oder andere Gründe vorliegen.
  • Integration in andere Jetpack-Bibliotheken wie Hilt

UI-Logik und ihr Statusinhaber

Die UI-Logik ist die Logik, die auf Daten angewendet wird, die von der Benutzeroberfläche selbst bereitgestellt werden. Dies kann auf den Status von UI-Elementen oder auf UI-Datenquellen wie die Permissions API oder Resources zurückzuführen sein. Statusinhaber, die UI-Logik verwenden, haben in der Regel die folgenden Attribute:

  • Erstellt den UI-Status und verwaltet den Status der UI-Elemente
  • Überlebt die Activity-Neuerstellung nicht: Statusinhaber, die in der UI-Logik gehostet werden, hängen oft von Datenquellen aus der UI selbst ab. Wenn versucht wird, diese Informationen über Konfigurationsänderungen hinweg aufzubewahren, führt dies meistens zu einem Speicherleck. Wenn Zustandsinhaber Daten über Konfigurationsänderungen hinweg beibehalten müssen, müssen sie die Daten an eine andere Komponente delegieren, die besser für die Wiederherstellung von Activity geeignet ist. In Jetpack Compose werden kompilierbare UI-Elementzustände, die mit remembered-Funktionen erstellt wurden, häufig an rememberSaveable delegiert, um den Status bei der Neuerstellung von Activity beizubehalten. Beispiele für solche Funktionen sind rememberScaffoldState() und rememberLazyListState().
  • Enthält Verweise auf UI-bezogene Datenquellen: Datenquellen wie Lebenszyklus-APIs und Ressourcen können sicher referenziert und gelesen werden, da der Inhaber des UI-Logikstatus denselben Lebenszyklus wie die UI hat.
  • Ist in mehreren UIs wiederverwendbar: Verschiedene Instanzen desselben UI-Logikstatusinhabers können in verschiedenen Teilen der App wiederverwendet werden. Beispielsweise kann ein Statusinhaber für die Verwaltung von Nutzereingabeereignissen für eine Chipgruppe auf einer Suchseite für Filter-Chips und auch für das Feld „An“ für Empfänger einer E-Mail verwendet werden.

Der UI-Logikstatusinhaber wird in der Regel mit einer einfachen Klasse implementiert. Das liegt daran, dass die UI selbst für die Erstellung des UI-Logikstatusinhabers verantwortlich ist und der Inhaber des UI-Logikstatus denselben Lebenszyklus wie die UI selbst hat. In Jetpack Compose ist der Zustandsinhaber beispielsweise Teil der Komposition und folgt dem Lebenszyklus der Komposition.

Dies wird im folgenden Beispiel im Now in Android-Beispiel veranschaulicht:

In Android wird jetzt ein einfacher Klassenstatus-Halter verwendet, um die UI-Logik zu verwalten.
Abbildung 5: Beispiel-App "Now in Android"

Im Beispiel „Now in Android“ wird je nach Bildschirmgröße des Geräts entweder eine untere App-Leiste oder eine Navigationsleiste angezeigt. Bei kleineren Bildschirmen wird die untere App-Leiste und bei größeren Bildschirmen die Navigationsleiste verwendet.

Da die Logik zur Auswahl des geeigneten Navigations-UI-Elements in der zusammensetzbaren Funktion NiaApp nicht von der Geschäftslogik abhängt, kann sie von einem einfachen Klassenstatusinhaber namens NiaAppState verwaltet werden:

@Stable
class NiaAppState(
    val navController: NavHostController,
    val windowSizeClass: WindowSizeClass
) {

    // UI logic
    val shouldShowBottomBar: Boolean
        get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
            windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact

    // UI logic
    val shouldShowNavRail: Boolean
        get() = !shouldShowBottomBar

   // UI State
    val currentDestination: NavDestination?
        @Composable get() = navController
            .currentBackStackEntryAsState().value?.destination

    // UI logic
    fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }

     /* ... */
}

Im obigen Beispiel sind die folgenden Details zum NiaAppState zu beachten:

  • Überlebt die Activity-Neuerstellung nicht: NiaAppState ist remembered in der Zusammensetzung. Dazu wird sie mit der zusammensetzbaren Funktion rememberNiaAppState gemäß den Compose-Namenskonventionen erstellt. Nachdem das Activity neu erstellt wurde, geht die vorherige Instanz verloren und es wird eine neue Instanz mit allen ihren Abhängigkeiten übergeben, die der neuen Konfiguration der neu erstellten Activity entsprechen. Diese Abhängigkeiten können neu sein oder aus der vorherigen Konfiguration wiederhergestellt werden. Beispielsweise wird rememberNavController() im NiaAppState-Konstruktor verwendet und an rememberSaveable delegiert, um den Status bei der Neuerstellung von Activity beizubehalten.
  • Enthält Verweise auf UI-bezogene Datenquellen: Verweise auf navigationController, Resources und andere ähnliche Lebenszyklustypen können sicher in NiaAppState abgelegt werden, da sie denselben Lebenszyklusbereich haben.

Wählen Sie zwischen einem ViewModel und einer einfachen Klasse für einen Staatsinhaber aus.

In den obigen Abschnitten hängt die Auswahl zwischen einem ViewModel- und einem einfachen Klassenstatusinhaber von der Logik ab, die auf den UI-Status und den Datenquellen angewendet wird, auf die die Logik arbeitet.

Das folgende Diagramm zeigt zusammenfassend die Position von Statusinhabern in der UI State-Produktionspipeline:

Daten fließen von der Datenerzeugungsschicht zur UI-Ebene.
Abbildung 6: Statusinhaber in der Produktionspipeline des UI-Status. Pfeile bedeuten Datenfluss.

Letztendlich sollten Sie den UI-Status mit den Statusinhabern erstellen, die dem Speicherort am nächsten sind. Weniger formell sollten Sie den Status so niedrig wie möglich halten und gleichzeitig die ordnungsgemäße Inhaberschaft aufrechterhalten. Wenn Sie Zugriff auf die Geschäftslogik benötigen und der UI-Status beibehalten werden soll, solange ein Bildschirm aufgerufen werden kann, auch bei der Neuerstellung von Activity, ist ViewModel eine gute Wahl für die Implementierung des Statusinhabers in der Geschäftslogik. Für einen kurzlebigen UI-Status und UI-Logik sollte eine einfache Klasse ausreichen, deren Lebenszyklus ausschließlich von der UI abhängt.

Staatsinhaber können mehrere

Inhaber von Bundesstaaten können von anderen Inhabern abhängig sein, solange die Abhängigkeiten eine gleiche oder kürzere Lebensdauer haben. Beispiele:

  • Ein Inhaber eines UI-Logikstatus kann von einem anderen Inhaber der Benutzeroberflächenlogik abhängig sein.
  • kann ein Statusinhaber auf Bildschirmebene von einem UI-Logikstatusinhaber abhängig sein.

Das folgende Code-Snippet zeigt, wie Compose's DrawerState von einem anderen internen Statusinhaber SwipeableState abhängt und wie der UI-Logikstatus einer App von DrawerState abhängen kann:

@Stable
class DrawerState(/* ... */) {
  internal val swipeableState = SwipeableState(/* ... */)
  // ...
}

@Stable
class MyAppState(
  private val drawerState: DrawerState,
  private val navController: NavHostController
) { /* ... */ }

@Composable
fun rememberMyAppState(
  drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
  navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
  MyAppState(drawerState, navController)
}

Ein Beispiel für eine Abhängigkeit, die einen Statusinhaber überdauert, wäre ein UI-Logikstatusinhaber, der von einem Inhaber auf Bildschirmebene abhängig ist. Dies würde die Wiederverwendbarkeit des kurzlebigen Zustandsinhabers verringern und ihm Zugriff auf mehr Logik und Status bieten, als er tatsächlich benötigt.

Wenn der kurzlebige Statusinhaber bestimmte Informationen von einem übergeordneten Statusinhaber benötigt, übergeben Sie nur die benötigten Informationen als Parameter, anstatt die Instanz des Statusinhabers zu übergeben. Im folgenden Code-Snippet empfängt die UI-Logikstatus-Halterungsklasse nur die, die sie als Parameter von ViewModel benötigt, anstatt die gesamte ViewModel-Instanz als Abhängigkeit zu übergeben.

class MyScreenViewModel(/* ... */) {
  val uiState: StateFlow<MyScreenUiState> = /* ... */
  fun doSomething() { /* ... */ }
  fun doAnotherThing() { /* ... */ }
  // ...
}

@Stable
class MyScreenState(
  // DO NOT pass a ViewModel instance to a plain state holder class
  // private val viewModel: MyScreenViewModel,

  // Instead, pass only what it needs as a dependency
  private val someState: StateFlow<SomeState>,
  private val doSomething: () -> Unit,

  // Other UI-scoped types
  private val scaffoldState: ScaffoldState
) {
  /* ... */
}

@Composable
fun rememberMyScreenState(
  someState: StateFlow<SomeState>,
  doSomething: () -> Unit,
  scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
  MyScreenState(someState, doSomething, scaffoldState)
}

@Composable
fun MyScreen(
  modifier: Modifier = Modifier,
  viewModel: MyScreenViewModel = viewModel(),
  state: MyScreenState = rememberMyScreenState(
    someState = viewModel.uiState.map { it.toSomeState() },
    doSomething = viewModel::doSomething
  ),
  // ...
) {
  /* ... */
}

Das folgende Diagramm zeigt die Abhängigkeiten zwischen der UI und verschiedenen Statusinhabern des vorherigen Code-Snippets:

UI abhängig vom Statusinhaber der UI-Logik und vom Statusinhaber auf Bildschirmebene
Abbildung 7: Benutzeroberfläche abhängig von verschiedenen Statusinhabern. Pfeile bedeuten Abhängigkeiten.

Produktproben

In den folgenden Google-Beispielen wird die Verwendung von Staatsinhabern auf der UI-Ebene veranschaulicht. Sehen Sie sich diese Tipps in der Praxis an: