Posiadacze stanów i stan interfejsu użytkownika

W przewodniku po warstwach interfejsu omawiamy jednokierunkowy przepływ danych (UDF) do tworzenia stanu interfejsu warstwy UI i zarządzania nim.

Dane przepływają jednokierunkowo z warstwy danych do interfejsu użytkownika.
Rys. 1. Jednokierunkowy przepływ danych

Podkreślamy też korzyści z przekazania zarządzania UDF specjalnej klasie nazywanej właścicielem stanu. Właściciela stanu możesz wdrożyć za pomocą klasy ViewModel lub zwykłej klasy. Ten dokument zawiera bliższe informacje na temat podmiotów państwowych i roli, jaką odgrywają one w warstwie interfejsu.

Na końcu tego dokumentu wiesz, jak zarządzać stanem aplikacji w warstwie interfejsu użytkownika, czyli potoku produkcyjnym stanu UI. Powinniście wiedzieć:

  • Omówienie typów stanów interfejsu użytkownika w warstwie interfejsu.
  • Omówienie typów logiki, które działają na tych stanach interfejsu, w warstwie interfejsu.
  • Dowiedz się, jak wybrać odpowiednią implementację podmiotu państwowego, np. ViewModel lub prostą klasę.

Elementy potoku produkcyjnego stanu UI

Stan interfejsu użytkownika i generowana przez niego logika definiują warstwę UI.

Stan interfejsu

Stan interfejsu użytkownika to właściwość opisująca interfejs użytkownika. Są 2 rodzaje stanu interfejsu:

  • Stan interfejsu ekranu to element, który ma być widoczny na ekranie. Klasa NewsUiState może na przykład zawierać artykuły z wiadomościami i inne informacje potrzebne do renderowania interfejsu użytkownika. Ten stan jest zwykle połączony z innymi warstwami hierarchii, ponieważ zawiera dane aplikacji.
  • Stan elementu interfejsu odnosi się do właściwości niepowiązanych z elementami interfejsu, które wpływają na sposób ich renderowania. Element interfejsu może być wyświetlany lub ukryty i może mieć określoną czcionkę oraz jej rozmiar i kolor. W widokach danych Androida widok danych samodzielnie zarządza tym stanem, ponieważ jest on z założenia stanowy i udostępnia metody do modyfikowania stanu lub wysyłania do niego zapytań. Przykładami są metody get i set klasy TextView dotyczącej tekstu. W Jetpack Compose stan jest spoza elementu kompozycyjnego i można przenieść go nawet w sąsiedztwo obiektu kompozycyjnego do wywołującego funkcję kompozycyjnego lub do elementu stanu. Przykładem może być ScaffoldState dla funkcji kompozycyjnej Scaffold.

Logiczna

Stan interfejsu użytkownika nie jest usługą statyczną, ponieważ dane aplikacji i zdarzenia użytkownika powodują zmianę stanu interfejsu w czasie. Logika określa specyfikę zmiany, m.in. które części stanu interfejsu się zmieniły, dlaczego i kiedy powinna się ona zmienić.

Logika tworzy stan interfejsu
Rysunek 2. Logika tworzenia stanu interfejsu

Logika logiczna aplikacji może być zarówno logiką biznesową, jak i logiką UI:

  • Logika biznesowa to implementacja wymagań dotyczących usługi w przypadku danych aplikacji. Przykładem może być dodanie artykułu do zakładek w aplikacji czytnika wiadomości, gdy użytkownik kliknie przycisk. Ta logika zapisywania zakładek do pliku lub bazy danych jest zwykle umieszczana w domenie lub warstwach danych. Właściciel stanu zazwyczaj przekazuje tę logikę do tych warstw, wywołując udostępniane przez nie metody.
  • Logika interfejsu jest powiązana ze sposobem wyświetlania stanu interfejsu na ekranie. Może to być na przykład uzyskanie odpowiedniej podpowiedzi na pasku wyszukiwania, gdy użytkownik wybierze kategorię, przewinięcie listy do konkretnego elementu na liście lub logika nawigacji po kliknięciu przycisku przez użytkownika.

Cykl życia Androida oraz typy stanu i logiki UI

Warstwa interfejsu składa się z 2 części: 1 zależnej i niezależnej od cyklu życia UI. To rozdzielenie określa źródła danych dostępne dla każdej części i dlatego wymaga różnych typów stanu i logiki UI.

  • Niezależna od cyklu życia interfejsu: ta część warstwy UI zajmuje się warstwami tworzenia danych (warstwami danych lub domen) i jest definiowana przez logikę biznesową. Cykl życia, zmiany w konfiguracji i odtwarzanie Activity w interfejsie użytkownika mogą mieć wpływ na to, czy potok produkcyjny stanu UI jest aktywny, ale nie mają wpływu na poprawność tworzonych danych.
  • Zależne od cyklu życia interfejsu: ta część warstwy UI zajmuje się logiką UI i jest bezpośrednio określana przez zmiany cyklu życia lub konfiguracji. Te zmiany wpływają bezpośrednio na poprawność źródeł danych odczytywanych w ramach tych danych, dlatego ich stan może się zmienić tylko wtedy, gdy cykl życia jest aktywny. Mogą to być na przykład uprawnienia w czasie działania oraz pobieranie zasobów zależnych od konfiguracji, takich jak zlokalizowane ciągi znaków.

Podsumowanie powyższych informacji znajdziesz w tabeli:

Niezależny od cyklu życia interfejsu użytkownika Zależne od cyklu życia interfejsu użytkownika
Logika biznesowa Logika UI
Stan interfejsu ekranu

Potok produkcyjny stanu interfejsu użytkownika

Potok produkcyjny stanu UI odnosi się do czynności wykonywanych w celu wygenerowania stanu UI. Te kroki obejmują zastosowanie określonych wcześniej typów logiki i całkowicie zależą od potrzeb Twojego interfejsu użytkownika. Niektóre interfejsy użytkownika mogą czerpać korzyści zarówno z części potoku niezależnych od cyklu życia UI, jak i z tych zależnych od tego cyklu (albo z żadnego z nich).

Oznacza to, że prawidłowe są te permutacje potoku warstwy UI:

  • Stan UI generowany i zarządzany przez sam interfejs użytkownika. Na przykład prosty licznik podstawowy wielokrotnego użytku:

    @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")
            }
        }
    }
    
  • Logika interfejsu → UI. Dotyczy to na przykład pokazywania lub ukrywania przycisku, który pozwala użytkownikowi przejść na początek listy.

    @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()
        }
    }
    
  • Logika biznesowa → UI. Element interfejsu wyświetlający zdjęcie bieżącego użytkownika na ekranie.

    @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)
    }
    
  • Logika biznesowa → logika interfejsu → UI. Element interfejsu, który można przewijać, aby wyświetlić na ekranie odpowiednie informacje o danym stanie.

    @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)
                }
            }
        }
    }
    

Jeśli do potoku produkcji stanu interfejsu stosuje się oba rodzaje logiki, logika biznesowa musi być zawsze stosowana przed logiką UI. Próba zastosowania logiki biznesowej po logice UI sugeruje, że logika biznesowa zależy od logiki UI. W poniższych sekcjach omówiliśmy ten problem i dogłębnie przyjrzymy się różnym typom logiki i ich typom.

Przepływy danych z warstwy danych do interfejsu użytkownika
Rys. 3. Stosowanie logiki w warstwie interfejsu

Właściciele państw i ich obowiązki

Obowiązkiem właściciela stanu jest zapisywanie stanu w taki sposób, aby aplikacja mogła go odczytać. W sytuacjach, gdy potrzebna jest logika, pełni ona rolę pośrednika i zapewnia dostęp do źródeł danych obsługujących wymaganą logikę. W ten sposób organ władzy przekazuje funkcje logiczne odpowiedniemu źródłu danych.

Dzięki temu:

  • Proste interfejsy: interfejs po prostu wiąże swój stan.
  • Łatwość obsługi: logika zdefiniowana w właścicielu stanu można powtarzać bez zmiany interfejsu użytkownika.
  • Możliwość testowania: interfejs użytkownika i jego stanowa logika produkcyjna można testować niezależnie.
  • Czytelność: czytelnicy kodu widzą różnice między kodem prezentacji interfejsu a kodem produkcyjnym stanu UI.

Każdy element interfejsu jest w relacji 1:1 z odpowiadającym mu stanem, niezależnie od jego rozmiaru i zakresu. Dodatkowo właściciel stanu musi być w stanie zaakceptować i przetworzyć dowolne działanie użytkownika, które może spowodować zmianę stanu interfejsu, oraz musi spowodować zmianę stanu.

Typy właścicieli stanów

Podobnie jak w przypadku stanów i logiki interfejsu, w warstwie interfejsu użytkownika istnieją 2 typy stanów i logiki, które są definiowane przez ich relację z cyklem życia interfejsu:

  • Właściciel stanu logiki biznesowej.
  • Przechowujący stan logiki interfejsu.

W sekcjach poniżej dokładniej omawiamy typy właścicieli stanów, zaczynając od operatora stanu logiki biznesowej.

Logika biznesowa i określony stan

Właściciele stanów logiki biznesowej przetwarzają zdarzenia użytkownika i przekształcają dane z warstw danych lub domen na stan interfejsu ekranu. Aby zapewnić użytkownikom optymalne wrażenia przy uwzględnieniu zmian cyklu życia Androida i konfiguracji aplikacji, właściciele stanów korzystający z logiki biznesowej powinni mieć te właściwości:

Właściwość Szczegóły
Generuje stan interfejsu użytkownika Właściciele stanów logiki biznesowej odpowiadają za tworzenie stanu interfejsu użytkownika. Taki stan interfejsu jest często wynikiem przetwarzania zdarzeń użytkownika i odczytywania danych z domeny i warstw danych.
Utrzymywanie aktywności dzięki aktywności fizycznej Właściciele stanów logiki biznesowej zachowują potoki przetwarzania stanu i stanu podczas przywracania Activity, co ułatwia obsługę użytkownikom. W przypadkach, gdy nie można zachować właściciela stanu i jest on ponownie tworzony (zwykle po zakończeniu procesu), musi mieć możliwość łatwego odtworzenia ostatniego stanu, aby zapewnić spójne wrażenia użytkownika.
Posiadanie długotrwałego stanu Posiadacze stanów logiki biznesowej są często używane do zarządzania stanem miejsc docelowych nawigacji. W efekcie często zachowują swój stan niezależnie od zmian w nawigacji do momentu usunięcia ich z wykresu nawigacyjnego.
jest unikalny dla interfejsu użytkownika i nie można go ponownie używać; Właściciele stanów logiki biznesowej zwykle wywołują stan określonej funkcji aplikacji, na przykład TaskEditViewModel lub TaskListViewModel, i dlatego mają zastosowanie tylko do tej funkcji aplikacji. Ten sam stan może obsługiwać te funkcje aplikacji w różnych formatach. Na przykład wersje aplikacji na urządzenia mobilne, telewizory i tablety mogą używać tego samego stanu logiki biznesowej.

Weźmy na przykład miejsce docelowe nawigacji dla autora w aplikacji „Now in Android” (Teraz w Androidzie):

Aplikacja Now w Androidzie pokazuje, jak miejsce docelowe nawigacji reprezentujące główną funkcję aplikacji powinno mieć własny, unikalny stan logiki biznesowej.
Rysunek 4.: Aplikacja Now w Androidzie

Jako właściciel stanu logiki biznesowej AuthorViewModel tworzy w takim przypadku stan interfejsu:

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

    val uiState: StateFlow<AuthorScreenUiState> = …

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

Zwróć uwagę, że AuthorViewModel ma opisane wcześniej atrybuty:

Właściwość Szczegóły
Produkcja: AuthorScreenUiState AuthorViewModel odczytuje dane z tabel AuthorsRepository i NewsRepository, a następnie używa ich do wygenerowania AuthorScreenUiState. Stosuje się też logikę biznesową, gdy użytkownik chce obserwować Author lub przestać je obserwować, przekazując użytkownikowi AuthorsRepository.
Ma dostęp do warstwy danych Wystąpienie AuthorsRepository i NewsRepository są przekazywane do niego w swoim konstruktorze, co pozwala zaimplementować logikę biznesową obserwującą Author.
Można korzystać z rozrywki Activity Ponieważ zasób jest zaimplementowany z elementem ViewModel, zostanie on zachowany przy szybkim odtwarzaniu Activity. W przypadku śmierci procesu można odczytywać obiekt SavedStateHandle, aby dostarczyć minimalną ilość informacji wymaganych do przywrócenia stanu interfejsu z warstwy danych.
Ma długotrwałe użytkowanie Zakres ViewModel jest ograniczony do wykresu nawigacyjnego, więc jeśli z grafu nawigacyjnego nie usuniesz miejsca docelowego autora, stan interfejsu użytkownika w tabeli StateFlow uiState pozostanie w pamięci. Zastosowanie wywołania StateFlow zwiększa też korzyści wynikające z zastosowania logiki biznesowej, która prowadzi do stanu leniwego stanu, ponieważ stan jest tworzony tylko wtedy, gdy występuje kolektor stanu UI.
Jest unikalny dla interfejsu użytkownika. Element AuthorViewModel ma zastosowanie tylko do miejsca docelowego nawigacji po autorze i nie można go użyć ponownie w żadnym innym miejscu. Jeśli logika biznesowa jest wykorzystywana ponownie w miejscach docelowych nawigacji, musi ona być zawarta w komponencie o zakresie na poziomie danych lub domeny.

Model ViewModel jako posiadacz stanu logiki biznesowej

Dzięki zaletom ViewModels podczas tworzenia aplikacji na Androida są one odpowiednie do zapewniania dostępu do logiki biznesowej i przygotowywania danych aplikacji do prezentacji na ekranie. Niektóre z nich to:

  • Operacje aktywowane przez obiekty ViewModele przestają obowiązywać po wprowadzeniu zmian w konfiguracji.
  • Integracja z nawigacją:
    • Nawigacja przechowuje obiekty ViewModele w pamięci podręcznej, gdy ekran znajduje się na stosie wstecznym. Pamiętaj, aby wcześniej wczytane dane były dostępne od razu po powrocie do miejsca docelowego. Jest to trudniejsze do zrobienia, gdy właściciel stanu może śledzić cykl życia ekranu kompozycyjnego.
    • Model ViewModel jest też czyszczony, gdy miejsce docelowe zostanie wydzielone na tylnym stosie, dzięki czemu stan zostanie automatycznie wyczyszczony. Różni się to od nasłuchiwania metod usuwania elementów kompozycyjnych, które mogą mieć miejsce z wielu powodów, np. z powodu przejścia na nowy ekran, zmiany konfiguracji lub innych powodów.
  • integrację z innymi bibliotekami Jetpack, takimi jak Hilt;

Logika UI i jej stan

Logika interfejsu to logika, która działa na danych dostarczanych przez sam interfejs użytkownika. Może to być stan elementów interfejsu lub źródła danych interfejsu, takie jak interfejs API uprawnień lub Resources. Właściciele stanów, którzy wykorzystują logikę interfejsu, zwykle mają te właściwości:

  • Generuje stan interfejsu użytkownika i zarządza stanem elementów interfejsu.
  • Nie można kontynuować przywracania usługi Activity: właściciele stanów hostowani w logice UI często są zależni od źródeł danych z samego interfejsu użytkownika. Próby zachowania tych informacji w ramach zmian w konfiguracji częściej powodują wyciek pamięci. Jeśli właściciele stanów chcą, aby dane pozostawały zachowane po wszystkich zmianach w konfiguracji, muszą przekazać je do innego komponentu, który lepiej zachowuje się w przypadku Activity. Na przykład w Jetpack Compose stany elementów interfejsu kompozycyjnego tworzone za pomocą funkcji remembered często są przekazywane do interfejsu rememberSaveable, aby zachować stan w odtwarzaniu Activity. Przykłady takich funkcji to rememberScaffoldState() i rememberLazyListState().
  • Zawiera odniesienia do źródeł danych ograniczonych do interfejsu użytkownika: można bezpiecznie odwoływać się do źródeł danych, takich jak interfejsy API cyklu życia i zasoby, i odczytywać je, ponieważ właściciel stanu logiki UI ma ten sam cykl życia co interfejs użytkownika.
  • Można go używać wielokrotnie w wielu interfejsach: różne instancje tej samej logiki interfejsu mogą być używane w różnych częściach aplikacji. Na przykład podmiot, który zarządza zdarzeniami wprowadzania danych użytkownika w grupie elementów, może być używany na stronie wyszukiwania elementów filtra, a także w polu „Do” w przypadku odbiorców e-maila.

Operator stanu logicznego interfejsu użytkownika jest zwykle implementowany za pomocą zwykłej klasy. Dzieje się tak, ponieważ sam interfejs użytkownika jest odpowiedzialny za tworzenie logiki stanu UI, a właściciel logiki UI ma taki sam cykl życia jak sam interfejs. Na przykład w Jetpack Compose element stanu jest częścią kompozycji i jest zgodny z cyklem życia kompozycji.

Powyższa wizja ilustruje ten przykład w przykładzie teraz w Androidzie:

Teraz w Androidzie do zarządzania logiką interfejsu użytkownika używany jest zwykły element stanu klasy
Rysunek 5. Przykładowa aplikacja Now na Androida

Przykładowy interfejs Now w Androidzie pokazuje dolny pasek aplikacji lub pasek nawigacji w zależności od rozmiaru ekranu urządzenia. Na mniejszych ekranach aplikacji znajduje się dolny pasek aplikacji, a większe – pasek nawigacji.

Logika wyboru odpowiedniego elementu interfejsu użytkownika używanego w funkcji kompozycyjnej NiaApp nie zależy od logiki biznesowej, więc może nią zarządzać prosty właściciel stanu klasy o nazwie NiaAppState:

@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) { /* ... */ }

     /* ... */
}

W przykładzie powyżej istotne są te informacje dotyczące właściwości NiaAppState:

  • Nie można kontynuować odtwarzania za pomocą funkcji Activity: NiaAppState ma wartość remembered w Kompozycji przez utworzenie jej za pomocą funkcji kompozycyjnej rememberNiaAppState zgodnie z konwencjami nazewnictwa przy tworzeniu wiadomości. Po odtworzeniu instancji Activity poprzednia instancja zostaje utracona i tworzona jest nowa instancja ze wszystkimi przekazanymi zależnościami, odpowiednio do nowej konfiguracji odtworzonego zasobu Activity. Zależności te mogą być nowe lub przywrócone z poprzedniej konfiguracji. Na przykład element rememberNavController() jest używany w konstruktorze NiaAppState i przekazuje do rememberSaveable stan w Activityrekonstrukcji.
  • Zawiera odwołania do źródeł danych ograniczonych do interfejsu użytkownika: odniesienia do typów ograniczonych do navigationController, Resources i innych podobnych typów cyklu życia mogą być bezpiecznie przechowywane w usłudze NiaAppState, ponieważ mają ten sam zakres cyklu życia.

Wybierz model ViewModel lub klasę zwykłego użytkownika dla posiadacza stanu

Z powyższych sekcji wybór między ViewModel a zwykłym obiektem stanu klasy sprowadza się do logiki stosowanej do stanu interfejsu użytkownika oraz do źródeł danych, na których działa ta logika.

Poniższy diagram przedstawia pozycję właścicieli stanów w potoku produkcyjnym UI State:

Dane przechodzą z warstwy danych do warstwy UI
Rysunek 6. Właściciele stanów w potoku produkcyjnym stanu UI. Strzałki oznaczają przepływ danych.

Ostatecznie stan interfejsu należy utworzyć, korzystając z właścicieli stanu najbliżej miejsca, w którym jest używany. W prostszy sposób należy utrzymywać stan na najniższym poziomie przy zachowaniu odpowiedniego poziomu własności. Jeśli potrzebujesz dostępu do logiki biznesowej i chcesz, aby stan interfejsu był niezmienny, dopóki użytkownik może przechodzić na ekran, nawet w przypadku odtwarzania Activity, znak ViewModel będzie doskonałym rozwiązaniem w przypadku wdrożenia właściciela stanu logiki biznesowej. W przypadku krótszego stanu interfejsu i logiki interfejsu powinna wystarczyć prosta klasa, której cykl życia zależy wyłącznie od interfejsu.

Właściciele stanowe mogą być składane

Właściciele państwowi mogą polegać na innych posiadaczach stanowych, o ile te zależności będą miały taki sam lub krótszy okres obowiązywania. Przykłady:

  • właściciel stanu logiki UI może zależeć od innego posiadacza stanu logiki UI.
  • właściciel stanu na poziomie ekranu może zależeć od właściciela stanu logiki interfejsu.

Poniższy fragment kodu pokazuje, w jaki sposób obiekt DrawerState tworzenia wiadomości zależy od innego elementu wewnętrznego stanu (SwipeableState) oraz w jaki sposób właściciel stanu logiki interfejsu aplikacji może zależeć od parametru DrawerState:

@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)
}

Przykładem zależności, która nie jest właściwa dla właściciela stanu, jest operator stanu logicznego interfejsu zależny od właściciela stanu na poziomie ekranu. Zmniejszy to możliwości ponownego wykorzystania osób o krótszym czasie życia i da mu dostęp do większej liczby logiki i stanu, niż rzeczywiście potrzebuje.

Jeśli właściciel stanu o krótszym okresie życia potrzebuje określonych informacji od właściciela stanu o wyższym zakresie, przekazuj tylko te informacje, których potrzebuje jako parametr, zamiast przekazywać instancję właściciela stanu. Na przykład w poniższym fragmencie kodu klasa posiadacza stanu logiki interfejsu otrzymuje z obiektu ViewModel tylko to, czego potrzebuje, jako parametry, zamiast przekazywać całą instancję ViewModel jako zależność.

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
  ),
  // ...
) {
  /* ... */
}

Poniższy diagram przedstawia zależności między interfejsem użytkownika a różnymi właścicielami stanu w poprzednim fragmencie kodu:

Interfejs w zależności od operatora logiki UI i stanu na poziomie ekranu
Rysunek 7. Interfejs w zależności od stanu. Strzałki oznaczają zależności.

Próbki

Poniższe przykłady Google pokazują wykorzystanie właścicieli stanów w warstwie interfejsu. Zapoznaj się z nimi, aby zastosować te wskazówki w praktyce: