Warstwa interfejsu

Interfejs użytkownika służy do wyświetlania danych aplikacji na ekranie. Interfejs użytkownika jest też głównym punktem interakcji użytkownika. Gdy dane się zmienią, czy to w wyniku interakcji użytkownika (np. naciśnięcia przycisku), czy danych wejściowych z zewnątrz (np. odpowiedzi sieci), interfejs użytkownika zostanie zaktualizowany, aby odzwierciedlać te zmiany. Interfejs użytkownika jest wizualną reprezentacją stanu aplikacji pobranego z warstwy danych.

Dane aplikacji, które uzyskujesz z warstwy danych, mają jednak zwykle inny format niż informacje, które chcesz wyświetlać. Możesz na przykład potrzebować tylko części danych do interfejsu lub połączyć 2 różne źródła danych, aby wyświetlić informacje istotne dla użytkownika. Niezależnie od zastosowanej logiki musisz przekazać interfejsowi wszystkie informacje potrzebne do pełnego renderowania. Warstwa interfejsu to potok, który przekształca zmiany danych aplikacji w formę, którą interfejs może wyświetlić, a następnie ją wyświetla.

W typowym przypadku elementy interfejsu warstwy interfejsu zależą od obiektów przechowujących stan, które z kolei zależą od klas z warstwy danych lub opcjonalnej warstwy domeny.
Rysunek 1. Rola warstwy interfejsu w architekturze aplikacji.

Podstawowe studium przypadku

Rozważ aplikację, która pobiera artykuły z wiadomościami, aby użytkownik mógł je przeczytać. Aplikacja ma ekran artykułów, na którym wyświetlane są dostępne artykuły, a zalogowani użytkownicy mogą dodawać do zakładek te, które szczególnie ich zainteresowały. Biorąc pod uwagę, że w każdym momencie może być dostępnych wiele artykułów, czytelnik musi mieć możliwość przeglądania ich według kategorii. Podsumowując, aplikacja umożliwia użytkownikom:

  • Wyświetl artykuły dostępne do przeczytania.
  • Przeglądaj artykuły według kategorii.
  • zalogować się i dodać do zakładek wybrane artykuły;
  • uzyskać dostęp do niektórych funkcji premium, jeśli spełniasz wymagania;
Przykładowa aplikacja z wiadomościami, w której wyświetlają się podglądy artykułów, z których jeden jest dodany do zakładek.
Rysunek 2. Przykładowa aplikacja do wiadomości na potrzeby studium przypadku interfejsu.

W sekcjach poniżej użyjemy tego przykładu jako studium przypadku, aby przedstawić zasady jednokierunkowego przepływu danych i problemy, które pomagają rozwiązać te zasady w kontekście architektury aplikacji dla warstwy interfejsu.

Architektura warstwy interfejsu

Termin interfejs odnosi się do elementów interfejsu, takich jak kontenery i funkcje kompozycyjne, które wyświetlają dane. Do tworzenia interfejsów Androida zalecamy korzystanie z zestawu narzędzi Jetpack Compose. Ponieważ warstwa danych służy do przechowywania danych aplikacji, zarządzania nimi i udostępniania do nich dostępu, warstwa interfejsu musi wykonać te czynności:

  1. Korzystanie z danych aplikacji i przekształcanie ich w dane, które interfejs może łatwo renderować.
  2. Pobieranie danych, które można renderować w interfejsie, i przekształcanie ich w elementy interfejsu do wyświetlania użytkownikowi.
  3. Przetwarzaj zdarzenia danych wejściowych użytkownika z tych złożonych elementów interfejsu i w razie potrzeby odzwierciedlaj ich efekty w danych interfejsu.
  4. W razie potrzeby powtórz kroki od 1 do 3.

W pozostałej części tego przewodnika pokazujemy, jak zaimplementować warstwę interfejsu, która wykonuje te czynności. W tym przewodniku znajdziesz informacje o tych zadaniach i pojęciach:

  • Określanie stanu interfejsu
  • Jednokierunkowy przepływ danych (UDF) jako sposób tworzenia stanu interfejsu i zarządzania nim
  • Jak udostępniać stan interfejsu za pomocą typów danych obserwowanych zgodnie z zasadami UDF
  • Jak wdrożyć interfejs, który korzysta z obserwowalnego stanu interfejsu

Najważniejsza z nich to definicja stanu interfejsu.

Określanie stanu interfejsu

studium przypadku opisanym wcześniej interfejs wyświetla listę artykułów wraz z metadanymi każdego z nich. Informacje, które aplikacja wyświetla użytkownikowi, to stan interfejsu.

Innymi słowy, jeśli interfejs to to, co widzi użytkownik, stan interfejsu to to, co aplikacja mówi, że użytkownik powinien widzieć. Interfejs użytkownika jest wizualną reprezentacją stanu interfejsu. Wszelkie zmiany stanu interfejsu są natychmiast odzwierciedlane w interfejsie.

Interfejs użytkownika jest wynikiem powiązania elementów interfejsu na ekranie ze stanem interfejsu.
Rysunek 3. Interfejs użytkownika jest wynikiem powiązania elementów interfejsu na ekranie ze stanem interfejsu.

Rozważmy studium przypadku: aby spełnić wymagania aplikacji Wiadomości, informacje potrzebne do pełnego renderowania interfejsu użytkownika można umieścić w NewsUiStateklasie danych zdefiniowanej w ten sposób:

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,
    ...
)

Więcej informacji o stanie interfejsu znajdziesz w artykule Stan i Jetpack Compose.

Niezmienność

Definicja stanu interfejsu w poprzednim przykładzie jest niezmienna. Główną zaletą tego rozwiązania jest to, że obiekty niezmienne gwarantują stan aplikacji w danym momencie. Dzięki temu interfejs może skupić się na swojej podstawowej roli: odczytywaniu stanu i odpowiednim aktualizowaniu elementów interfejsu. Nigdy nie modyfikuj stanu interfejsu bezpośrednio w interfejsie, chyba że interfejs jest jedynym źródłem danych. Naruszenie tej zasady powoduje, że to samo źródło informacji ma wiele źródeł prawdy, co prowadzi do niespójności danych i trudnych do wykrycia błędów.

Weźmy pod uwagę omówione wcześniej studium przypadku. Jeśli flaga bookmarked w obiekcie NewsItemUiState ze stanu interfejsu zostanie zaktualizowana w klasie Activity, ta flaga będzie konkurować z warstwą danych jako źródło stanu artykułu dodanego do zakładek. Klasy danych niemutowalnych są bardzo przydatne w zapobieganiu tego rodzaju niespójności.

Konwencje nazewnictwa w tym przewodniku

W tym przewodniku klasy stanu interfejsu są nazywane na podstawie funkcji ekranu lub jego części, które opisują. Obowiązuje następująca konwencja:

funkcjonalność + UiState.

Na przykład stan ekranu wyświetlającego wiadomości może być oznaczony jako NewsUiState, a stan artykułu w liście artykułów jako NewsItemUiState.

Zarządzanie stanem za pomocą jednokierunkowego przepływu danych

W poprzedniej sekcji ustaliliśmy, że stan interfejsu to niezmienny zrzut szczegółów potrzebnych do renderowania interfejsu. Dynamiczny charakter danych w aplikacjach oznacza jednak, że stan ten może się z czasem zmieniać. Może to być spowodowane interakcją użytkownika lub innymi zdarzeniami, które modyfikują dane bazowe używane do wypełniania aplikacji.

W przypadku tych interakcji może być przydatny mediator, który je przetwarza, definiując logikę, która ma być stosowana do każdego zdarzenia, i przekształcając źródła danych, aby utworzyć stan interfejsu. Chociaż te interakcje i ich logika mogą być zawarte w interfejsie, może to szybko stać się nieporęczne, ponieważ interfejs przejmuje zbyt dużą odpowiedzialność. Może to też wpłynąć na możliwość testowania, ponieważ wynikowy kod jest ściśle powiązany. O ile stan interfejsu nie jest bardzo prosty, upewnij się, że jego jedynym zadaniem jest pobieranie i wyświetlanie stanu interfejsu.

W tej sekcji omówimy jednokierunkowy przepływ danych (UDF), czyli wzorzec architektury, który pomaga wymusić ten zdrowy podział obowiązków.

Zmienne stanu

Stanowe są klasami odpowiedzialnymi za generowanie stanu interfejsu i logiki potrzebnej do jego utworzenia. Obiekty przechowujące stan mają różne rozmiary w zależności od zakresu zarządzanych przez nie elementów interfejsu, od pojedynczego widżetu, takiego jak dolny pasek aplikacji, po cały ekran lub miejsce docelowe nawigacji.

W tym drugim przypadku typową implementacją jest instancja ViewModel, chociaż w zależności od wymagań aplikacji może wystarczyć prosta klasa. Na przykład aplikacja Wiadomości z studium przypadku używa klasy NewsViewModel jako kontenera stanu do tworzenia stanu interfejsu ekranu wyświetlanego w tej sekcji.

Istnieje wiele sposobów modelowania współzależności między interfejsem a jego producentem stanu. Interakcję między interfejsem a jego ViewModelklasą można w dużej mierze rozumieć jako dane wejściowe zdarzenia i wynikające z nich dane wyjściowe stanu. Zależność tę można przedstawić w sposób pokazany na poniższym diagramie:

Dane aplikacji przepływają z warstwy danych do ViewModelu. Stan interfejsu przepływa z ViewModel do elementów interfejsu, a zdarzenia przepływają z elementów interfejsu z powrotem do ViewModel.
Rysunek 4. Diagram przedstawiający działanie funkcji zdefiniowanych przez użytkownika w architekturze aplikacji.

Wzorzec, w którym stan przepływa w dół, a zdarzenia w górę, nazywa się jednokierunkowym przepływem danych (UDF). Wpływ tego wzorca na architekturę aplikacji jest następujący:

  • ViewModel przechowuje stan i udostępnia go interfejsowi. Stan interfejsu to dane aplikacji przekształcone przez ViewModel.
  • Interfejs użytkownika powiadamia ViewModel o zdarzeniach użytkownika.
  • ViewModel obsługuje działania użytkownika i aktualizuje stan.
  • Zaktualizowany stan jest przekazywany z powrotem do interfejsu, aby go wyrenderować.
  • Powyższe czynności należy powtórzyć w przypadku każdego zdarzenia, które powoduje zmianę stanu.

W przypadku miejsc docelowych lub ekranów nawigacji klasa ViewModel współpracuje z repozytoriami lub klasami przypadków użycia, aby pobierać dane i przekształcać je w stan interfejsu, uwzględniając przy tym efekty zdarzeń, które mogą powodować zmiany stanu. Wspomniane wcześniej studium przypadku zawiera listę artykułów, z których każdy ma tytuł, opis, źródło, imię i nazwisko autora, datę publikacji oraz informację o tym, czy został dodany do zakładek. Interfejs każdego artykułu wygląda tak:

Pojedynczy artykuł z aplikacji z przypadkami użycia. Interfejs zawiera miniaturę, tytuł artykułu, autora, szacowany czas czytania i ikonę zakładki.
Rysunek 5. Interfejs elementu artykułu w aplikacji do studiów przypadków.

Przykładem zdarzenia, które może powodować zmiany stanu, jest użytkownik, który chce dodać artykuł do zakładek. Jako producent stanu ViewModel jest odpowiedzialny za zdefiniowanie całej logiki wymaganej do wypełnienia wszystkich pól w stanie interfejsu i przetwarzania zdarzeń potrzebnych do pełnego renderowania interfejsu.

Zdarzenie interfejsu użytkownika występuje, gdy użytkownik doda artykuł do zakładek. Klasa ViewModel
    powiadamia warstwę danych o zmianie stanu. Warstwa danych zachowuje zmianę danych i aktualizuje dane aplikacji. Nowe dane aplikacji z artykułem dodanym do zakładek są przekazywane do ViewModelu, który następnie tworzy nowy stan interfejsu i przekazuje go do elementów interfejsu do wyświetlenia.
Rysunek 6. Diagram ilustrujący cykl zdarzeń i danych w funkcji UDF.

W sekcjach poniżej przyjrzymy się bliżej zdarzeniom, które powodują zmiany stanu, i sposobom ich przetwarzania za pomocą funkcji UDF.

Rodzaje logiki

Dodanie artykułu do zakładek to przykład logiki biznesowej, ponieważ zwiększa wartość aplikacji. Więcej informacji znajdziesz na stronie warstwy danych. Istnieją jednak różne rodzaje logiki, które warto zdefiniować:

  • Logika biznesowa to wdrożenie wymagań dotyczących produktu w przypadku danych aplikacji. Jak już wspomnieliśmy, przykładem może być dodanie artykułu do zakładek w aplikacji z studium przypadku. Logika biznesowa zwykle znajduje się w warstwach domeny lub danych, ale nigdy w warstwie interfejsu.
  • Logika działania interfejsu lub logika interfejsu określa sposób wyświetlania zmian stanu na ekranie. Może to być np. uzyskanie odpowiedniego tekstu do wyświetlenia na ekranie za pomocą Androida Resources, przejście do określonego ekranu po kliknięciu przycisku przez użytkownika lub wyświetlenie wiadomości użytkownika na ekranie za pomocą toastu lub paska powiadomień.

Logikę interfejsu użytkownika umieszczaj w interfejsie, a nie w obiekcie ViewModel, zwłaszcza jeśli dotyczy ona typów interfejsu, takich jak Context. Jeśli interfejs staje się coraz bardziej złożony i chcesz przekazać logikę interfejsu do innej klasy, aby zwiększyć testowalność i rozdzielić obowiązki, możesz utworzyć prostą klasę jako kontener stanu. Proste klasy utworzone w interfejsie mogą korzystać z zależności pakietu Android SDK, ponieważ są zgodne z cyklem życia interfejsu. Obiekty ViewModel mają dłuższy okres istnienia.

Więcej informacji o obiektach stanu i ich roli w kontekście tworzenia interfejsu znajdziesz w przewodniku po stanie w Jetpack Compose.

Dlaczego warto korzystać z funkcji UDF?

Model UDF przedstawia cykl produkcji stanu, jak pokazano na rysunku 4. Oddziela też miejsce, w którym powstają zmiany stanu, miejsce, w którym są one przekształcane, i miejsce, w którym są ostatecznie wykorzystywane. Ten podział pozwala interfejsowi użytkownika robić dokładnie to, co sugeruje jego nazwa: wyświetlać informacje, obserwując zmiany stanu, i przekazywać intencje użytkownika, przekazując te zmiany do ViewModelu.

Inaczej mówiąc, funkcja UDF umożliwia:

  • Spójność danych Interfejs użytkownika ma jedno źródło wiarygodnych danych.
  • Możliwość testowania. Źródło stanu jest odizolowane, dzięki czemu można je testować niezależnie od interfejsu.
  • Łatwość konserwacji. Zmiana stanu przebiega według dobrze zdefiniowanego wzorca, w którym mutacje są wynikiem zarówno zdarzeń użytkownika, jak i źródeł danych, z których są pobierane.

Udostępnianie stanu interfejsu

Po zdefiniowaniu stanu interfejsu i określeniu sposobu zarządzania jego produkcją kolejnym krokiem jest przedstawienie wyprodukowanego stanu w interfejsie.

Jeśli używasz funkcji UDF do zarządzania stanem produkcji, możesz traktować wygenerowany stan jako strumień, czyli z czasem powstaje wiele wersji stanu. Udostępnij stan interfejsu w obserwowalnym kontenerze danych, np. StateFlow. Dzięki temu interfejs może reagować na wszelkie zmiany stanu bez konieczności ręcznego pobierania danych bezpośrednio z ViewModelu. Dzięki temu zawsze masz w pamięci podręcznej najnowszą wersję stanu interfejsu, co jest przydatne do szybkiego przywracania stanu po zmianach konfiguracji.

class NewsViewModel(...) : ViewModel() {

    val uiState: NewsUiState = 
}

Wprowadzenie do przepływów w Kotlinie znajdziesz w artykule Przepływy w Kotlinie na Androidzie. Aby dowiedzieć się, jak używać StateFlow jako obiektu do przechowywania danych dostępnych do obserwacji, zapoznaj się z ćwiczeniem Stan zaawansowany i efekty uboczne w Jetpack Compose.

Jeśli dane udostępniane interfejsowi są stosunkowo proste, warto je opakować w typ stanu interfejsu, ponieważ odzwierciedla to relację między emisją zmiennej stanu a powiązanym z nim ekranem lub elementem interfejsu. W miarę jak element interfejsu staje się bardziej złożony, można go łatwo dodać do definicji stanu interfejsu, aby uwzględnić dodatkowe informacje potrzebne do renderowania elementu interfejsu.

Częstym sposobem tworzenia strumienia UiState jest udostępnianie właściwości mutableStateOfprivate set, przy czym stan jest zmienny w ViewModelu, ale tylko do odczytu w interfejsie.

class NewsViewModel(...) : ViewModel() {

    var uiState by mutableStateOf(NewsUiState())
        private set

    ...
}

ViewModel może następnie udostępniać metody, które wewnętrznie zmieniają stan, publikując aktualizacje, które UI może wykorzystywać. Rozważmy na przykład sytuację, w której musisz wykonać działanie asynchroniczne. Możesz uruchomić korutynę za pomocą funkcji viewModelScope, a następnie po jej zakończeniu zaktualizować stan modyfikowalny.

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

W poprzednim przykładzie klasa NewsViewModel próbuje pobrać artykuły z określonej kategorii, a następnie odzwierciedla wynik tej próby – sukces lub porażkę – w stanie interfejsu, który może odpowiednio na to zareagować. Więcej informacji o obsłudze błędów znajdziesz w sekcji Wyświetlanie błędów na ekranie.

Uwagi dodatkowe

Oprócz powyższych wskazówek podczas udostępniania stanu interfejsu użytkownika weź pod uwagę te kwestie:

  • Używaj jednego obiektu stanu interfejsu do obsługi stanów, które są ze sobą powiązane. Dzięki temu jest mniej niespójności, a kod jest łatwiejszy do zrozumienia. Jeśli udostępnisz listę artykułów i liczbę zakładek w 2 różnych strumieniach, może się zdarzyć, że jeden z nich zostanie zaktualizowany, a drugi nie. Jeśli używasz jednego strumienia, oba elementy są aktualizowane. Ponadto niektóre rodzaje logiki biznesowej mogą wymagać połączenia źródeł. Możesz na przykład wyświetlać przycisk dodawania do zakładek tylko wtedy, gdy użytkownik jest zalogowany i subskrybuje płatną usługę informacyjną. Klasę stanu interfejsu możesz zdefiniować w ten sposób:

    data class NewsUiState(
        val isSignedIn: Boolean = false,
        val isPremium: Boolean = false,
        val newsItems: List<NewsItemUiState> = listOf()
    )
    
    val NewsUiState.canBookmarkNews: Boolean get() = isSignedIn && isPremium
    

    W tej deklaracji widoczność przycisku dodawania do zakładek jest właściwością pochodną 2 innych właściwości. W miarę jak logika biznesowa staje się bardziej złożona, coraz ważniejsze staje się posiadanie jednej klasy UiState, w której wszystkie właściwości są od razu dostępne.

  • Stany interfejsu: jeden czy wiele strumieni? Główną zasadą wyboru między udostępnianiem stanu interfejsu w jednym strumieniu lub w wielu strumieniach jest relacja między emitowanymi elementami. Największe zalety pojedynczego strumienia to wygoda i spójność danych: konsumenci stanu zawsze mają dostęp do najnowszych informacji. W niektórych przypadkach oddzielne strumienie stanu z ViewModelu mogą być odpowiednie:

    • Niezwiązane typy danych: niektóre stany potrzebne do renderowania interfejsu użytkownika mogą być od siebie całkowicie niezależne. W takich przypadkach koszty łączenia tych różnych stanów mogą przewyższać korzyści, zwłaszcza jeśli jeden z nich jest aktualizowany częściej niż drugi.

    • UiState porównywanie: im więcej pól zawiera obiekt UiState, tym większe prawdopodobieństwo, że strumień wyemituje dane w wyniku aktualizacji jednego z tych pól. Elementy interfejsu nie mają mechanizmu różnicowego, który pozwalałby określić, czy kolejne emisje są różne czy takie same, więc każda emisja powoduje aktualizację elementu interfejsu. Oznacza to, że może być konieczne zastosowanie środków zaradczych z użyciem metod interfejsu API, takich jak distinctUntilChanged().Flow

Więcej informacji o renderowaniu i stanie interfejsu znajdziesz w artykule Cykl życia funkcji kompozycyjnych.

Korzystanie ze stanu interfejsu

Aby wykorzystać strumień obiektów UiState w interfejsie, użyj operatora terminala dla używanego typu danych dostępnych do obserwacji. W przypadku przepływów Kotlin użyj np. metody collect() lub jej odmian.

Korzystając w interfejsie z obiektów danych dostępnych do obserwacji, weź pod uwagę cykl życia interfejsu. Nie obserwuj stanu interfejsu, gdy element kompozycyjny nie jest wyświetlany użytkownikowi. Więcej informacji na ten temat znajdziesz w tym poście na blogu. Podczas korzystania z przepływów najlepiej jest obsługiwać kwestie związane z cyklem życia za pomocą odpowiedniego zakresu współprogramu i interfejsu collectAsStateWithLifecycle API:

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

Pokaż operacje w toku

Prostym sposobem na przedstawienie stanów ładowania w klasie UiState jest pole logiczne:

data class NewsUiState(
    val isFetchingArticles: Boolean = false,
    ...
)

Wartość tego flagi określa, czy w interfejsie jest widoczny pasek postępu.

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

Wyświetlanie błędów na ekranie

Wyświetlanie błędów w interfejsie jest podobne do wyświetlania operacji w toku, ponieważ w obu przypadkach można łatwo użyć wartości logicznych, które wskazują, czy błąd lub operacja występuje. Błędy mogą jednak zawierać powiązany komunikat, który ma być przekazany użytkownikowi, lub powiązane z nimi działanie, które ponawia nieudaną operację. Dlatego, gdy trwająca operacja jest wczytywana lub nie, stany błędów mogą wymagać modelowania za pomocą klas danych, które zawierają metadane odpowiednie dla kontekstu błędu.

Rozważmy poprzedni przykład, w którym podczas pobierania artykułów wyświetlany był pasek postępu. Jeśli ta operacja spowoduje błąd, możesz wyświetlić użytkownikowi co najmniej jeden komunikat ze szczegółowymi informacjami o tym, co poszło nie tak.

data class Message(val id: Long, val message: String)

data class NewsUiState(
    val userMessages: List<Message> = listOf(),
    ...
)

Możesz wtedy wyświetlać komunikaty o błędach w interfejsie w postaci elementów interfejsu, takich jak paski powiadomień. Więcej informacji o tym, jak są generowane i wykorzystywane zdarzenia interfejsu, znajdziesz w artykule Zdarzenia interfejsu.

Wątki i współbieżność

Upewnij się, że wszystkie działania wykonywane w obiekcie ViewModel są bezpieczne dla wątku głównego, czyli można je wywoływać z wątku głównego. Warstwy danych i domeny odpowiadają za przenoszenie pracy do innego wątku.

Jeśli ViewModel wykonuje długotrwałe operacje, jest też odpowiedzialny za przeniesienie tej logiki do wątku w tle. Korutyny Kotlin to świetny sposób na zarządzanie operacjami współbieżnymi, a komponenty architektury Jetpack zapewniają wbudowaną obsługę tych operacji. Więcej informacji o używaniu współprogramów w aplikacjach na Androida znajdziesz w artykule Współprogramy Kotlin na Androidzie.

Zmiany w nawigacji aplikacji są często spowodowane zdarzeniami. Na przykład po zalogowaniu się zajęć SignInViewModel pole isSignedIn może mieć wartość true.UiState Używaj takich wyzwalaczy jak te opisane w sekcji Używanie stanu interfejsu, ale odłóż implementację użycia do komponentu Navigation.

Więcej informacji o nawigacji po interfejsie znajdziesz w rozdziale Nawigacja 3.

Paging

Biblioteka Paging jest używana w interfejsie z typem o nazwie PagingData. Ponieważ PagingData reprezentuje i zawiera elementy, które mogą się zmieniać z czasem (czyli nie jest typem niezmiennym), nie przedstawiaj go w niezmiennym stanie interfejsu. Zamiast tego udostępnij go z ViewModel niezależnie we własnym strumieniu.

Poniższy przykład pokazuje interfejs Compose API biblioteki Paging:

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

Animacje

Aby zapewnić płynne przejścia w nawigacji najwyższego poziomu, możesz poczekać, aż drugi ekran wczyta dane, zanim rozpoczniesz animację.

Więcej informacji o przejściach w nawigacji znajdziesz w sekcjach Nawigacja 3Przejścia elementów wspólnych w Compose.

Dodatkowe materiały

Wyświetlanie treści

Przykłady

Poniższe przykłady Google pokazują, jak używać warstwy interfejsu. Zapoznaj się z nimi, aby zobaczyć te wskazówki w praktyce: