State i Jetpack Compose

Stan w aplikacji to dowolna wartość, która może się z czasem zmieniać. To bardzo ogólne określenie i obejmuje wszystko, od bazy danych sal po zmienną w zajęcia.

Wszystkie aplikacje na Androida wyświetlają stan. Kilka przykładów stanu w Androidzie Aplikacje:

  • pasek powiadomień, który wyświetla się, gdy nie można nawiązać połączenia sieciowego;
  • Post na blogu i związane z nim komentarze.
  • Faliste animacje przycisków odtwarzanych po kliknięciu przez użytkownika.
  • Naklejki, które użytkownik może narysować na zdjęciu.

W Jetpack Compose możesz wyraźnie określić, gdzie i jak przechowujesz i używasz plików w aplikacji na Androida. Ten przewodnik skupia się na połączeniu między stanem za pomocą funkcji kompozycyjnych oraz interfejsów API oferowanych przez Jetpack Compose do większej pracy ze stanami .

Stan i kompozycja

Funkcja tworzenia wiadomości ma charakter deklaratywny, dlatego jedynym sposobem jej zaktualizowania jest wywołanie funkcji funkcję kompozycyjną z nowymi argumentami. Te argumenty reprezentują Stan interfejsu. Po każdej aktualizacji stanu następuje zmiana kompozycji. Jako W efekcie aplikacje takie jak TextField nie aktualizują się automatycznie imperatywnych widoków XML. Funkcja kompozycyjna musi mieć jawną informację o nowym stanie aby została odpowiednio zaktualizowana.

@Composable
private fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello!",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(
            value = "",
            onValueChange = { },
            label = { Text("Name") }
        )
    }
}

Jeśli go uruchomisz i spróbujesz wpisać tekst, nic się nie stanie. To jest bo TextField nie aktualizuje się samodzielnie – gdy jest value zmian parametrów. Wynika to ze sposobu, w jaki działają kompozycja i rekompozycja Utwórz.

Więcej informacji na temat początkowej kompozycji i zmiany kompozycji znajdziesz tutaj: myśl w tworzeniu wiadomości

Stan w elementach kompozycyjnych

Funkcje kompozycyjne mogą korzystać z funkcji remember Interfejs API do przechowywania obiektu w pamięci. Wartość obliczona przez funkcję remember to przechowywane w Kompozycji podczas a środkowa wartość jest zwracana podczas jej ponownego komponowania. remember może służyć do przechowywania zarówno obiektów zmiennych, jak i stałych.

mutableStateOf tworzy obiekt dostrzegalny MutableState<T>, czyli dostrzegalny typ zintegrowany ze środowiskiem wykonawczym tworzenia.

interface MutableState<T> : State<T> {
    override var value: T
}

Wszelkie zmiany w value planują ponowne kompozycje funkcji kompozycyjnych z informacją o treści: value.

Obiekt MutableState można zadeklarować w funkcji kompozycyjnej na 3 sposoby:

  • val mutableState = remember { mutableStateOf(default) }
  • var value by remember { mutableStateOf(default) }
  • val (value, setValue) = remember { mutableStateOf(default) }

Te deklaracje są równoważne i są podane jako cukier składni dla różnego rodzaju używania państwa. Wybierz taki, który generuje najłatwiejszy do odczytania kod w tworzonej przez Ciebie funkcji kompozycyjnej.

Składnia delegata by wymaga tych importów:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

Zapamiętanej wartości możesz użyć jako parametru dla innych elementów kompozycyjnych, a nawet funkcji logicznych w instrukcjach, aby zmieniać wyświetlane elementy kompozycyjne. Na przykład, jeśli Nie chcesz wyświetlać powitania, jeśli nazwa jest pusta, wpisz stan w Instrukcja if:

@Composable
fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        var name by remember { mutableStateOf("") }
        if (name.isNotEmpty()) {
            Text(
                text = "Hello, $name!",
                modifier = Modifier.padding(bottom = 8.dp),
                style = MaterialTheme.typography.bodyMedium
            )
        }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )
    }
}

remember pomaga w zachowaniu stanu w przypadku zmian, ale już nie i zachowywane w ramach zmian konfiguracji. Aby to zrobić, musisz użyć rememberSaveable rememberSaveable automatycznie zapisuje każdą wartość, która może zostać zapisano w folderze Bundle. W przypadku innych wartości możesz przekazać obiekt wygaszacza niestandardowego.

Inne obsługiwane typy stanu

Tworzenie nie wymaga używania MutableState<T> do zatrzymywania stanu; jego obsługuje inne dostrzegalne typy. Przed odczytaniem innego dostrzegalnego typu w Compose musisz przekonwertować do formatu State<T>, aby funkcje kompozycyjne mogły automatycznie komponować ponownie po zmianie stanu.

Twórz statki z funkcjami, które tworzą State<T> na podstawie wspólnych obserwowalnych funkcji używane w aplikacjach na Androida. Zanim zaczniesz korzystać z tych integracji, dodaj odpowiednie artefakty opisane poniżej:

  • Flow: collectAsStateWithLifecycle()

    collectAsStateWithLifecycle() zbiera wartości z: Flow w sposób uwzględniający cykl życia, dzięki czemu aplikacja może i oszczędzać zasoby aplikacji. Reprezentuje ostatnią wartość wyemitowaną przez Utwórz State. Używaj tego interfejsu API jako zalecanego sposobu gromadzenia przepływów w Aplikacje na Androida.

    W pliku build.gradle wymagana jest ta zależność (powinna być w wersji 2.6.0-beta01 lub nowszej):

Kotlin

dependencies {
      ...
      implementation("androidx.lifecycle:lifecycle-runtime-compose:2.6.2")
}

Odlotowe

dependencies {
      ...
      implementation "androidx.lifecycle:lifecycle-runtime-compose:2.6.2"
}
  • Flow: collectAsState()

    collectAsState jest podobny do collectAsStateWithLifecycle, ponieważ również zbiera wartości z tabeli Flow i przekształca je w ikonę Utwórz State.

    W przypadku kodu niezależnego od platformy zamiast tagu collectAsState collectAsStateWithLifecycle, która jest dostępna tylko na Androidzie.

    W przypadku funkcji collectAsState nie są wymagane dodatkowe zależności, ponieważ jest ona dostępne w języku: compose-runtime.

  • LiveData: observeAsState()

    observeAsState() zaczyna obserwować ten element (LiveData) i przedstawia jego wartości przez State.

    W pliku build.gradle wymagana jest ta zależność:

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-livedata:1.6.8")
}

Odlotowe

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-livedata:1.6.8"
}

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.6.8")
}

Odlotowe

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.6.8"
}

Kotlin

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava3:1.6.8")
}

Odlotowe

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava3:1.6.8"
}

Stanowa a bezstanowa

Funkcja kompozycyjna, która używa remember do przechowywania obiektu, tworzy stan wewnętrzny, zmienia się w stanową element kompozycyjny. HelloContent to przykład klasy stanowej jest kompozycyjna, ponieważ przechowuje i zmienia swój stan name wewnętrznie. Może to spowodować Jest przydatne w sytuacjach, w których rozmówca nie musi kontrolować stanu i może bez konieczności samodzielnego zarządzania organami władzy. Jednak elementy kompozycyjne z które trudniej jest przetestować i rzadziej nadaje się do użytku wewnętrznego.

Funkcja bezstanowa kompozycyjna to funkcja kompozycyjna, która nie zachowuje żadnego stanu. Łatwy do uzyskania bezstanowego rozwiązania jest m.in. użycie funkcji state dźwignię.

Gdy tworzysz elementy kompozycyjne wielokrotnego użytku, często warto udostępnić zarówno stanowy, i bezstanowej wersji elementu kompozycyjnego. Wersja stanowa to co jest wygodne dla osób, które nie dbają o stan, wersja jest niezbędna dla elementów wywołujących, które muszą kontrolować lub podnosić stan.

Podnośnik stanu

Przenoszenie stanu w funkcji tworzenia wiadomości to wzorzec przenoszenia stanu do wywołującego funkcji kompozycyjnej przekształcenia elementów kompozycyjnych do stanu bezstanowego. Ogólny wzorzec budowania stanu W Jetpack Compose należy zastąpić zmienną stanu dwoma parametrami:

  • value: T: bieżąca wartość do wyświetlenia
  • onValueChange: (T) -> Unit: zdarzenie, które prosi o zmianę wartości; gdzie T to nowa wartość proponowana

Nie dotyczy Cię jednak ograniczenie do onValueChange. Jeśli zdarzenia są bardziej szczegółowe dla funkcji kompozycyjnej, określ je za pomocą funkcji lambda.

Stan, który jest przenoszony w ten sposób, ma kilka ważnych właściwości:

  • Jedno źródło danych: przeniesienie stanu, zamiast jego duplikowania, dzięki czemu jest pewna, że istnieje tylko jedno źródło wiarygodnych danych. Pomoże to uniknąć błędów.
  • Zamknięta: tylko stanowe elementy kompozycyjne mogą zmieniać swój stan. Jest całkowicie wewnętrznych.
  • Możliwość udostępniania: stan przeciągania można udostępnić wielu elementom kompozycyjnym. Jeśli chcesz przeczytać treść „name” w innym narzędziu kompozycyjnym, podnoszącym aby to zrobić.
  • Możliwe do współdziałania: osoby wywołujące bezstanowe funkcje kompozycyjne mogą ignoruj lub zmodyfikuj zdarzenia przed zmianą stanu.
  • Rozłączone: stan bezstanowych elementów kompozycyjnych może być przechowywany. w dowolnym miejscu. Można na przykład przenieść name do ViewModel.

W tym przykładzie wyodrębnisz name i onValueChange z HelloContent i przenieś je w górę do kompozycji HelloScreen, która Wywołuje połączenie HelloContent.

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
    }
}

Podnosząc stan z HelloContent, łatwiej jest wyciągnąć wnioski które można komponować, używać ponownie w różnych sytuacjach i testować. HelloContent to oddzielone od sposobu przechowywania stanu. Usunięcie powiązania oznacza, że jeśli zmodyfikujesz lub zastąpisz HelloScreen, nie musisz zmieniać sposobu działania typu HelloContent .

Wzorzec, w którym następuje spadek stanu do momentu uporządkowania zdarzeń, jest nazywany jednokierunkowego przepływu danych. W tym przypadku stan zmienia się z HelloScreen do HelloContent, a liczba zdarzeń wzrasta od HelloContent do HelloScreen. Według zgodnie z jednokierunkowym przepływem danych, możesz odłączyć elementy kompozycyjne, które wyświetlają stan w interfejsie z tych części aplikacji, które zapisują i zmieniają stan.

Więcej informacji znajdziesz na stronie Gdzie przenieść stan.

Przywracanie stanu w funkcji tworzenia wiadomości

Interfejs API rememberSaveable działa podobnie do remember, ponieważ Zachowuje stan w ramach zmian, a także w ramach aktywności lub procesu za pomocą mechanizmu zapisanego stanu instancji. Dzieje się tak na przykład: po obróceniu ekranu.

Sposoby zapisywania stanu

Wszystkie typy danych dodane do usługi Bundle są zapisywane automatycznie. Jeśli Chcesz zapisać coś, czego nie można dodać do listy Bundle, istnieje kilka metod .

Parcelyzacja

Najprostszym rozwiązaniem jest dodanie funkcji @Parcelize do obiektu. Obiekt stanie się częścią pakietu i można go połączyć w pakiet. Dla: przykładowo, ten kod tworzy typ danych City z możliwością parcelacji i zapisuje go w stanu.

@Parcelize
data class City(val name: String, val country: String) : Parcelable

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

Zapisywanie mapy

Jeśli z jakiegoś powodu @Parcelize jest nieodpowiedni, możesz użyć mapSaver, aby zdefiniować własną regułę konwertowania obiektu na zbiór wartości, system może zapisywać w Bundle.

data class City(val name: String, val country: String)

val CitySaver = run {
    val nameKey = "Name"
    val countryKey = "Country"
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },
        restore = { City(it[nameKey] as String, it[countryKey] as String) }
    )
}

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

Zapisywanie listy

Aby uniknąć konieczności definiowania kluczy mapy, możesz też użyć funkcji listSaver i użyj ich jako kluczy:

data class City(val name: String, val country: String)

val CitySaver = listSaver<City, Any>(
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

Posiadacze stanów w usłudze Compose

Prostym przenoszeniem stanów można zarządzać bezpośrednio w funkcjach kompozycyjnych. Jeśli jednak ilość stanu, której należy śledzić wzrost, lub logika w przypadku funkcji kompozycyjnych, warto przydzielić w zakresie logiki i obowiązków stanowych w odniesieniu do innych klas: właścicieli państw.

Zapoznaj się z dokumentacją na temat zbierania stanów w funkcji tworzenia wiadomości lub, ogólniej, Strona Właściciele stanów i stan interfejsu w przewodniku po architekturze, aby dowiedzieć się więcej.

Ponownie wywołuj zapamiętywanie obliczeń po zmianie kluczy

Interfejs remember API jest często używany razem z MutableState:

var name by remember { mutableStateOf("") }

W tym przypadku użycie funkcji remember sprawia, że wartość MutableState obowiązuje dalej przekomponowania.

Ogólnie funkcja remember przyjmuje parametr lambda calculation. Gdy remember to pierwsze uruchomienie, wywołuje funkcję lambda calculation i zapisuje jej wynik. W trakcie przekomponowanie, funkcja remember zwraca ostatnio zapisaną wartość.

Oprócz stanu buforowania za pomocą remember możesz też przechowywać dowolne obiekty w wyniku operacji w Kompozycji, której zainicjowanie jest kosztowne obliczeń. Możesz nie chcieć powtarzać tych obliczeń przy każdej zmianie kompozycji. Przykładem może być utworzenie drogiego obiektu ShaderBrush operacja:

val brush = remember {
    ShaderBrush(
        BitmapShader(
            ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
            Shader.TileMode.REPEAT,
            Shader.TileMode.REPEAT
        )
    )
}

Funkcja remember przechowuje wartość, dopóki nie opuści Kompozycji. Występuje jednak unieważnienia wartości z pamięci podręcznej. Interfejs API remember pobiera też key lub keys. Jeśli któryś z tych klawiszy się zmieni, następnym razem, gdy funkcja ponowne kompilowanie danych, remember unieważnia pamięć podręczną i wykonuje obliczenia blok lambda. Mechanizm ten daje Ci kontrolę nad czasem trwania w kompozycji. Obliczenie będzie obowiązywać do momentu wprowadzenia danych wejściowych zmiany, a nie do momentu, gdy zapamiętana wartość opuści Kompozycję.

Poniższe przykłady pokazują, jak działa ten mechanizm.

W tym fragmencie kodu utworzony jest ShaderBrush, który jest używany jako tło. obraz kompozycyjnego Box. remember przechowuje instancję ShaderBrush ponieważ odtwarzanie jest drogie, jak wyjaśniliśmy wcześniej. remember podejmowania avatarRes jako parametr key1, który jest wybranym obrazem tła. Jeśli avatarRes, pędzel ponownie komponuje się z nowym obrazem i stosuje się ponownie do Box. Może się tak zdarzyć, gdy użytkownik wybierze inny obraz z selektora.

@Composable
private fun BackgroundBanner(
    @DrawableRes avatarRes: Int,
    modifier: Modifier = Modifier,
    res: Resources = LocalContext.current.resources
) {
    val brush = remember(key1 = avatarRes) {
        ShaderBrush(
            BitmapShader(
                ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
                Shader.TileMode.REPEAT,
                Shader.TileMode.REPEAT
            )
        )
    }

    Box(
        modifier = modifier.background(brush)
    ) {
        /* ... */
    }
}

W następnym fragmencie kodu stan został przeniesiony do klasy zwykłej posiadacza stanu MyAppState Udostępnia funkcję rememberMyAppState do zainicjowania instancji klasy za pomocą metody remember. Ujawnienie takich funkcji w celu utworzenia które przetrwają ponowne kompozycje, jest typowym wzorcem w Compose. Funkcja rememberMyAppState odbiera windowSizeClass, który służy jako parametr key dla remember. Jeśli ten parametr się zmieni, aplikacja musi: odtwórz prostą klasę posiadacza stanu z najnowszą wartością. Może się tak zdarzyć, jeśli: na przykład obróci urządzenie.

@Composable
private fun rememberMyAppState(
    windowSizeClass: WindowSizeClass
): MyAppState {
    return remember(windowSizeClass) {
        MyAppState(windowSizeClass)
    }
}

@Stable
class MyAppState(
    private val windowSizeClass: WindowSizeClass
) { /* ... */ }

Funkcja tworzenia korzysta z implementacji klasy jest równe, aby określić, czy klucz ma zmienił i unieważnia przechowywane wartości.

Zapisuj stan z kluczami poza możliwością zmiany kompozycji

Interfejs API rememberSaveable jest otoką wokół remember, która może przechowywać w Bundle. Ten interfejs API sprawia, że stan może nie tylko przetrwać rekompozycja, ale też rekreacyjne i zainicjowane przez system śmierć. Funkcja rememberSaveable otrzymuje parametry input w tym samym celu, co remember otrzymuje keys. Pamięć podręczna zostaje unieważniona, gdy dowolne dane wejściowe . Przy następnym uruchomieniu funkcji rememberSaveable wykona ponowne wykonanie blok lambda do obliczeń.

W poniższym przykładzie rememberSaveable przechowuje userTypedQuery do typedQuery zmiany:

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

Więcej informacji

Więcej informacji o stanie i Jetpack Compose znajdziesz w tych artykułach: z dodatkowymi zasobami.

Próbki

Ćwiczenia z programowania

Filmy

Blogi

. .