Statusinhaber und UI-Status

Im Leitfaden zur UI-Ebene wird der unidirektionale Datenfluss (Universal Data Flow, UDF) zum Erstellen und Verwalten des UI-Status für die UI-Ebene erläutert.

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

Außerdem werden die Vorteile des Delegierens von UDF-Verwaltung an eine Sonderklasse, einen sogenannten State Owner, erläutert. Sie können einen Statusinhaber entweder über eine ViewModel oder eine einfache Klasse implementieren. In diesem Dokument werden Inhaber von Bundesstaaten und ihre Rolle auf der UI-Ebene näher betrachtet.

Am Ende dieses Dokuments sollten Sie wissen, wie Sie den Anwendungsstatus auf der UI-Ebene verwalten, d. h. in der Produktionspipeline für den UI-Status. Sie sollten Folgendes verstehen und wissen können:

  • Informieren Sie sich über die Arten von UI-Status, die auf der UI-Ebene vorhanden sind.
  • Informieren Sie sich über die Logiktypen, die für diese UI-Zustände auf der UI-Ebene ausgeführt werden.
  • Informieren Sie sich, wie Sie die geeignete Implementierung eines Statusinhabers, z. B. ViewModel oder einer einfachen Klasse, auswählen.

Elemente der Produktionspipeline für den UI-Status

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

UI-Status

UI state ist die Eigenschaft, mit der die UI beschrieben wird. Es gibt zwei Arten von UI-Status:

  • Der Bildschirm-UI-Status ist das, 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 App-Daten enthält.
  • Der UI-Elementstatus bezieht sich auf Eigenschaften von UI-Elementen, die ihr Rendering 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 sie von Natur aus zustandsorientiert ist. Sie stellt Methoden zum Ändern oder Abfragen des Status zur Verfügung. Ein Beispiel dafür sind die Methoden get und set der Klasse TextView für ihren Text. In Jetpack Composer befindet sich der Status außerhalb der zusammensetzbaren Funktion. Sie können sie sogar aus der unmittelbaren Nähe der zusammensetzbaren Funktion in die aufrufende zusammensetzbare Funktion oder einen Statusinhaber hochziehen. Ein Beispiel dafür ist ScaffoldState für die zusammensetzbare Funktion Scaffold.

Logik

Der UI-Status ist keine statische Eigenschaft, da Anwendungsdaten und Nutzerereignisse dazu führen, dass sich der UI-Status im Laufe der Zeit ändert. Logic ermittelt die Einzelheiten der Änderung, einschließlich der Teile des UI-Status, die sich geändert haben, warum sie geändert wurde und wann sie geändert werden sollte.

Logik generiert UI-Status
Abbildung 2: Logik als Ersteller des UI-Status

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

  • Die Geschäftslogik ist die Implementierung von Produktanforderungen an Anwendungsdaten. Beispiel: Einen Artikel in einer Newsreader-App als Lesezeichen speichern, wenn der Nutzer auf die Schaltfläche tippt. Die Logik zum Speichern eines Lesezeichens in einer Datei oder Datenbank wird normalerweise in der Domain oder den Datenschichten platziert. Der Zustandsinhaber delegiert diese Logik normalerweise an diese Ebenen, indem er die von ihnen verfügbaren Methoden aufruft.
  • Die UI-Logik bezieht sich darauf, wie der UI-Status auf dem Bildschirm angezeigt wird. So lässt sich beispielsweise der Hinweis auf der rechten Seite der Suchleiste anzeigen, wenn der Nutzer eine Kategorie ausgewählt hat, das Scrollen zu einem bestimmten Element in einer Liste oder die Navigationslogik zu einem bestimmten Bildschirm, wenn der Nutzer auf eine Schaltfläche klickt.

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

Die UI-Ebene besteht aus zwei Teilen: einem abhängig vom UI-Lebenszyklus und dem anderen unabhängig vom UI-Lebenszyklus. Durch diese Trennung werden die Datenquellen bestimmt, die für jeden Teil verfügbar sind. Daher sind unterschiedliche Arten von UI-Status und -Logik erforderlich.

  • Unabhängig vom UI-Lebenszyklus: Dieser Teil der UI-Ebene befasst sich mit den datenerzeugenden Ebenen der Anwendung (Daten- oder Domainebenen) und wird durch die Geschäftslogik definiert. Lebenszyklus, Konfigurationsänderungen und Activity-Neuerstellungen in der UI können sich darauf auswirken, ob die Produktionspipeline für den UI-Status aktiv ist. Die Gültigkeit der generierten Daten wird dadurch nicht beeinträchtigt.
  • Abhängig vom UI-Lebenszyklus: 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 der Status nur ändern, wenn sein Lebenszyklus aktiv ist. Beispiele dafür sind Laufzeitberechtigungen und das Abrufen konfigurationsabhängiger Ressourcen wie lokalisierte Strings.

Dies kann in der folgenden Tabelle zusammengefasst werden:

Vom UI-Lebenszyklus unabhängig UI-Lebenszyklus abhängig
Geschäftslogik UI-Logik
UI-Status des Bildschirms

Produktionspipeline für den UI-Status

Die Produktionspipeline für den UI-Status bezieht sich auf die Schritte, die zum Erzeugen 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 unterstützen möglicherweise sowohl vom UI-Lebenszyklus unabhängige als auch vom UI-Lebenszyklus abhängige Teile der Pipeline, entweder oder von keinem der beiden.

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

  • UI-Status, der von der UI selbst erzeugt und verwaltet wird. Zum Beispiel 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. Zum Beispiel das 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-Status 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. Wenn Sie versuchen, Geschäftslogik nach der UI-Logik anzuwenden, würde dies bedeuten, dass die Geschäftslogik von dieser abhängt. In den folgenden Abschnitten wird ausführlich erläutert, warum dies ein Problem ist.

Daten fließen von der datenerzeugenden Ebene zur UI.
Abbildung 3: Anwendung von Logik auf UI-Ebene

Staatliche Inhaber und ihre Verantwortlichkeiten

Die Aufgabe eines Staatsinhabers besteht darin, den Bundesstaat so zu speichern, dass die App ihn lesen kann. Wenn Logik erforderlich ist, fungiert sie als Mittler und bietet Zugriff auf die Datenquellen, die die erforderliche Logik hosten. Auf diese Weise delegiert der Staatsinhaber die Logik an die entsprechende Datenquelle.

Das hat folgende Vorteile:

  • Einfache UIs: Die UI bindet nur ihren Status.
  • Verwaltbarkeit: Die im Statusinhaber definierte Logik kann iteriert werden, ohne die Benutzeroberfläche selbst zu ändern.
  • Testbarkeit: Die UI und ihre Zustandsproduktionslogik können unabhängig voneinander getestet werden.
  • Lesbarkeit: Codeleser können die Unterschiede zwischen dem UI-Präsentationscode und dem Produktionscode für den UI-Status deutlich erkennen.

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

Arten staatlicher Inhaber

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

  • Statusinhaber der Geschäftslogik.
  • Statusinhaber der UI-Logik.

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

Geschäftslogik und Staatsinhaber

Statusinhaber der Geschäftslogik verarbeiten Nutzerereignisse und wandeln Daten aus den Daten- oder Domainebenen in die Anzeige des UI-Status um. Um eine optimale Nutzererfahrung im Hinblick auf den Android-Lebenszyklus und Änderungen der App-Konfiguration zu ermöglichen, sollten Inhaber von Status, die Geschäftslogik verwenden, die folgenden Eigenschaften haben:

Attribut Detail
Erzeugt den UI-Status Die Inhaber von Status der Geschäftslogik sind dafür verantwortlich, den UI-Status für ihre UIs zu generieren. Dieser UI-Status entsteht häufig, wenn Nutzerereignisse verarbeitet und Daten aus der Domain und den Datenschichten gelesen werden.
Durch Aktivitätenerholung beibehalten Die Inhaber von Status der Geschäftslogik behalten ihre Verarbeitungspipelines für den Status und Zustand bei der Activity-Neuerstellung bei, was für eine nahtlose Nutzererfahrung sorgt. Falls der Inhaber des Bundesstaats nicht beibehalten und neu erstellt werden kann (in der Regel nach dem Prozesstod), muss er den letzten Status problemlos wiederherstellen können, um für eine konsistente Nutzererfahrung zu sorgen.
Hat langlebigen Status Statusinhaber in der Geschäftslogik werden häufig verwendet, um den Status für Navigationsziele zu verwalten. Daher behalten sie ihren Status bei Navigationsänderungen häufig bei, bis sie aus dem Navigationsdiagramm entfernt werden.
Ist nur auf der Benutzeroberfläche verfügbar und kann nicht wiederverwendet werden Inhaber von Geschäftslogik-Zuständen erstellen in der Regel einen Status für eine bestimmte App-Funktion, z. B. ein TaskEditViewModel oder TaskListViewModel, und gelten daher immer nur für diese App-Funktion. Derselbe Inhaber kann diese App-Funktionen über verschiedene Formfaktoren hinweg unterstützen. Beispielsweise kann in den Smartphone-, TV- und Tablet-Versionen der App derselbe Status der Geschäftslogik verwendet werden.

Nehmen wir als Beispiel das Navigationsziel für Autoren in der App „Now in Android“:

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

AuthorViewModel fungiert als Statusinhaber der Geschäftslogik und generiert 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 umrissenen Attribute enthält:

Attribut Detail
Produktion: AuthorScreenUiState AuthorViewModel liest Daten aus AuthorsRepository und NewsRepository und verwendet diese Daten, um AuthorScreenUiState zu erzeugen. Außerdem wird Geschäftslogik angewendet, wenn der Nutzer einem Author folgen oder nicht mehr folgen möchte, indem er an AuthorsRepository delegiert.
Hat Zugriff auf die Datenschicht Eine Instanz von AuthorsRepository und NewsRepository wird in ihrem Konstruktor an sie übergeben. Dadurch kann die Geschäftslogik implementiert werden, die einem Author folgt.
Überlebt Activity Freizeit Da sie mit einem ViewModel implementiert wird, wird sie bei der schnellen Neuerstellung von Activity beibehalten. Falls der Prozess beendet ist, kann das SavedStateHandle-Objekt ausgelesen werden, um die Mindestmenge an Informationen bereitzustellen, die zum Wiederherstellen des UI-Status aus der Datenschicht erforderlich ist.
Besitzt langlebigen Bundesstaat ViewModel ist auf das Navigationsdiagramm beschränkt. Solange das Autorenziel nicht aus dem Navigationsdiagramm entfernt wird, bleibt der UI-Status in uiState StateFlow im Arbeitsspeicher. Die Verwendung von StateFlow bietet außerdem den Vorteil, dass die Anwendung der Geschäftslogik, die den Zustand erzeugt, verlangsamt wird, da der Zustand nur dann erzeugt wird, wenn ein Collector des UI-Status vorhanden ist.
Ist für die Benutzeroberfläche eindeutig AuthorViewModel gilt nur für das Navigationsziel des Autors und kann nirgendwo sonst wiederverwendet werden. Gibt es eine Geschäftslogik, die über Navigationsziele hinweg wiederverwendet wird, muss diese Geschäftslogik in eine Komponente auf Daten- oder Domainebene gekapselt werden.

ViewModel als Statusinhaber der Geschäftslogik

Dank der Vorteile von ViewModels in der Android-Entwicklung können Sie Zugriff auf die Geschäftslogik gewähren und Anwendungsdaten für die Darstellung auf dem Bildschirm vorbereiten. Zu diesen Vorteilen gehört Folgendes:

  • Von ViewModels ausgelöste Vorgänge überleben Konfigurationsänderungen.
  • Einbindung in Navigation:
    • Navigation speichert ViewModels im Cache, während sich der Bildschirm auf dem Back-Stack befindet. Dies ist wichtig, damit die zuvor geladenen Daten sofort verfügbar sind, wenn Sie zum Ziel zurückkehren. Dies ist etwas schwieriger mit einem Statushalter, der dem Lebenszyklus des zusammensetzbaren Bildschirms folgt.
    • Das ViewModel wird auch gelöscht, wenn das Ziel aus dem Back-Stack entfernt wird. Dadurch wird Ihr Status automatisch bereinigt. Dies unterscheidet sich von der Überwachung der zusammensetzbaren Verwertung, die aus verschiedenen Gründen erfolgen kann, z. B. wenn ein neuer Bildschirm aufgrund einer Konfigurationsänderung oder aus anderen Gründen aufgerufen wird.
  • Integration in andere Jetpack-Bibliotheken wie Hilt

UI-Logik und ihr Statusinhaber

UI-Logik ist eine Logik, die auf Daten arbeitet, die die UI selbst bereitstellt. Das kann sich auf den Status der UI-Elemente oder auf UI-Datenquellen wie der Berechtigungs-API oder Resources auswirken. Statusinhaber, die UI-Logik verwenden, haben in der Regel die folgenden Eigenschaften:

  • Erzeugt den UI-Status und verwaltet den Status der UI-Elemente.
  • Überlebt nicht die Neuerstellung von Activity: Statusinhaber, die in der UI-Logik gehostet werden, sind häufig von Datenquellen aus der UI selbst abhängig. Der Versuch, diese Informationen über Konfigurationsänderungen hinweg beizubehalten, führt häufiger als nicht zu einem Speicherleck. Wenn staatliche Inhaber Daten benötigen, die über Konfigurationsänderungen hinweg bestehen bleiben, müssen sie an eine andere Komponente delegieren, die besser für die Bewältigung der Activity-Neuerstellung geeignet ist. In Jetpack Compose werden beispielsweise mit remembered-Funktionen erstellte zusammensetzbare UI-Elementstatus 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 Datenquellen auf UI-Ebene: Datenquellen wie Lebenszyklus-APIs und -Ressourcen können sicher referenziert und gelesen werden, da der Statusinhaber der UI-Logik denselben Lebenszyklus wie die UI hat.
  • Ist über mehrere UIs hinweg wiederverwendbar: Verschiedene Instanzen desselben UI-Logik-Statusinhabers können in verschiedenen Teilen der App wiederverwendet werden. So kann ein Statusinhaber zum Verwalten von Nutzereingabeereignissen für eine Chipgruppe auf einer Suchseite für Filter-Chips und auch für das „An“-Feld für Empfänger einer E-Mail verwendet werden.

Der UI-Logik-Statusinhaber wird normalerweise mit einer einfachen Klasse implementiert. Das liegt daran, dass die UI selbst für die Erstellung des UI-Logikstatusinhabers verantwortlich ist und dieser den gleichen Lebenszyklus hat wie die UI selbst. In Jetpack Compose ist der Statusinhaber beispielsweise Teil der Zusammensetzung und folgt dem Lebenszyklus der Komposition.

Dies kann anhand des folgenden Beispiels im Beispiel von Now in Android veranschaulicht werden:

Verwendet jetzt in Android einen einfachen Klassenstatusinhaber, um die UI-Logik zu verwalten.
Abbildung 5: Die Beispiel-App „Now in Android“

Das Beispiel „Now in Android“ zeigt je nach Bildschirmgröße des Geräts entweder eine untere App-Leiste oder eine Navigationsleiste für die Navigation an. Für kleinere Bildschirme wird die untere App-Leiste verwendet, für größere Bildschirme die Navigationsleiste.

Da die Logik für die Entscheidung, welches Navigations-UI-Element in der zusammensetzbaren Funktion NiaApp verwendet wird, nicht von der Geschäftslogik abhängt, kann sie von einem einfachen Klassenstatus-Inhaber 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 wichtig:

  • Überlebt die Neuerstellung von Activity nicht: NiaAppState ist in der Zusammensetzung remembered, da sie mit der Funktion rememberNiaAppState unter Berücksichtigung der Namenskonventionen für die Zusammensetzung erstellt wird. Nachdem die Activity neu erstellt wurde, geht die vorherige Instanz verloren und es wird eine neue Instanz erstellt, bei der alle Abhängigkeiten übergeben werden, die der neuen Konfiguration der neu erstellten Activity entspricht. Diese Abhängigkeiten können neu 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 Datenquellen auf UI-Ebene: Verweise auf navigationController, Resources und andere ähnliche Typen auf Lebenszyklusebene können sicher in NiaAppState gespeichert werden, da sie denselben Lebenszyklus haben.

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

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

Zusammenfassend zeigt das folgende Diagramm die Position der Inhaber von Bundesstaaten in der Produktionspipeline für den UI-Status:

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

Letztendlich sollten Sie einen UI-Zustand mit den Statusinhabern erstellen, die dem Ort am nächsten sind, in dem sie verarbeitet werden. Weniger formell sollten Sie den Status so niedrig wie möglich halten, während Sie die Eigentumsrechte wahren. Wenn Sie Zugriff auf die Geschäftslogik benötigen und der UI-Status bestehen bleiben soll, solange ein Bildschirm aufgerufen werden kann (auch bei der Neuerstellung von Activity), ist ViewModel eine gute Wahl für die Implementierung des Statusinhabers für die Geschäftslogik. Für einen kürzeren UI-Status und eine UI-Logik sollte eine einfache Klasse ausreichen, deren Lebenszyklus ausschließlich von der UI abhängig ist.

Inhaber eines Bundesstaates sind zusammengesetzte

Inhaber von Bundesstaaten können sich von anderen Staatsinhabern abhängig machen, solange diese eine gleiche oder kürzere Lebensdauer haben. Beispiele:

  • Ein UI-Logik-Statusinhaber kann von einem anderen UI-Logik-Statusinhaber abhängig sein.
  • eines Statusinhabers auf Bildschirmebene von einem UI-Logikstatus-Inhaber abhängen kann.

Das folgende Code-Snippet zeigt, wie das DrawerState-Element von Compose vom anderen internen Statusinhaber SwipeableState abhängt und wie der Statusinhaber der UI-Logik 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 Inhaber des UI-Logikstatus, der von einem Inhaber eines Status auf Bildschirmebene abhängig ist. Dies würde die Wiederverwendbarkeit des kurzlebigen Statusinhabers verringern und ihm Zugriff auf mehr Logik und Zustand gewähren, als er tatsächlich benötigt.

Wenn der kurzlebige Bundesstaatinhaber bestimmte Informationen von einem übergeordneten Bundesstaatsinhaber benötigt, übergeben Sie nur die benötigten Informationen als Parameter, anstatt die Statusinhaberinstanz zu übergeben. Im folgenden Code-Snippet erhält die Status-Holder-Klasse UI-Logik nur das, was 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:

Benutzeroberfläche abhängig vom Inhaber des UI-Logikstatus und des Statusinhabers auf Bildschirmebene
Abbildung 7: Benutzeroberfläche abhängig von den Inhabern verschiedener Bundesstaaten. Pfeile stehen für Abhängigkeiten.

Produktproben

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