Na tej stronie poznasz cykl życia elementu kompozycyjnego i sposób, w jaki decyduje on, czy taki element wymaga zmiany kompozycji.
Omówienie cyklu życia
Jak wspomnieliśmy w dokumentacji zarządzania stanem, kompozycja opisuje interfejs użytkownika aplikacji i jest tworzona przez uruchomienie funkcji kompozycyjnych. Kompozycja to struktura drzewa elementów kompozycyjnych, która opisuje Twój interfejs użytkownika.
Gdy Jetpack Compose po raz pierwszy uruchomi kompozycje po raz pierwszy, podczas początkowej kompozycji będzie śledzić te kompozycje, które wywołujesz, aby opisać Twój interfejs w ramach kompozycji. Następnie, gdy stan aplikacji się zmieni, Jetpack Compose planuje ponowną kompozycję. Zmiana ma miejsce, gdy Jetpack Compose ponownie wykonuje kompozycje, które mogły się zmienić w zależności od zmian stanu, a następnie aktualizuje kompozycję, aby uwzględnić ewentualne zmiany.
Kompozycję można utworzyć tylko na podstawie początkowej kompozycji i zaktualizować ją przez zmianę kompozycji. Jedynym sposobem zmodyfikowania kompozycji jest jej zmiana.
Rysunek 1. Cykl życia elementu kompozycyjnego w kompozycji. Uruchamia się, zostaje ponownie skomponowana co najmniej 0 razy i opuszcza kompozycję.
Zmiana kompozycji jest zwykle wywoływana przez zmianę obiektu State<T>
. Funkcja Compose śledzi te elementy i uruchamia w niej wszystkie kompozycje, które odczytują dany element State<T>
, oraz wszelkie wywoływane przez nie obiekty kompozycyjne, których nie można pominąć.
Jeśli funkcja kompozycyjna zostanie wywołana wiele razy, w kompozycji zostanie umieszczonych wiele jej instancji. Każde połączenie ma swój własny cykl życia w kompozycji.
@Composable fun MyComposable() { Column { Text("Hello") Text("World") } }
Rysunek 2. Element MyComposable
w kompozycji. Jeśli funkcja kompozycyjna zostanie wywołana wiele razy, w kompozycji zostanie umieszczonych wiele jej instancji. Jeśli element ma inny kolor,
jest to osobny element.
Anatomia elementu kompozycyjnego w sekcji Kompozycja
Wystąpienie funkcji kompozycyjnej w ramach kompozycji jest identyfikowane przez stronę wywołania. Kompilator Compose traktuje każdą witrynę generującą połączenia jako osobne. Wywołanie funkcji kompozycyjnej z wielu witryn wywołujących powoduje utworzenie wielu wystąpień elementu kompozycyjnego w elemencie Composition.
Jeśli podczas zmiany kompozycji funkcja kompozycyjna wywołuje inne kompozycje niż jej poprzednią kompozycję, funkcja zidentyfikuje takie, które zostały lub nie wywołała. Natomiast w przypadku tych, które były wywoływane w obu kompozycjach, funkcja unika ich ponownego tworzenia, jeśli ich dane wejściowe się nie zmieniły.
Zachowanie tożsamości ma kluczowe znaczenie dla powiązania efektów ubocznych z komponentem, aby można było je zakończyć pomyślnie, a nie uruchamiać ponownie dla każdej zmiany kompozycji.
Przeanalizuj ten przykład:
@Composable fun LoginScreen(showError: Boolean) { if (showError) { LoginError() } LoginInput() // This call site affects where LoginInput is placed in Composition } @Composable fun LoginInput() { /* ... */ } @Composable fun LoginError() { /* ... */ }
We fragmencie kodu powyżej LoginScreen
warunkowo wywołuje funkcję LoginError
kompozycyjną i zawsze będzie wywoływać element LoginInput
kompozycyjny. Każde wywołanie ma unikalną pozycję witryny wywołania i pozycję źródła, której kompilator użyje do jej identyfikacji.
Rysunek 3. Reprezentacja funkcji LoginScreen
w kompozycji w przypadku zmiany stanu i zmiany kompozycji. Ten sam kolor oznacza, że element nie został ponownie skomponowany.
Mimo że instancja LoginInput
została wywołana jako pierwsza, zostanie wywołana jako druga, instancja LoginInput
zostanie zachowana we wszystkich rekompozycjach. Poza tym, ponieważ LoginInput
nie ma żadnych parametrów, które zmieniły się w czasie zmiany kompozycji, wywołanie LoginInput
zostanie pominięte przez funkcję Compose.
Dodaj więcej informacji, aby ułatwić inteligentne zmienianie kompozycji
Wielokrotne wywołanie funkcji kompozycyjnej spowoduje wielokrotne jej dodanie do kompozycji. W przypadku wielokrotnego wywoływania funkcji kompozycyjnej z tej samej witryny wywołania nie ma żadnych informacji umożliwiających jednoznaczną identyfikację każdego wywołania tego elementu kompozycyjnego, więc oprócz witryny wywołania jest używana kolejność wykonywania, tak aby instancje były różne. Takie zachowanie czasami nie wystarczy, ale w niektórych przypadkach może powodować niepożądane zachowanie.
@Composable fun MoviesScreen(movies: List<Movie>) { Column { for (movie in movies) { // MovieOverview composables are placed in Composition given its // index position in the for loop MovieOverview(movie) } } }
W powyższym przykładzie funkcja Compose korzysta z kolejności wykonania oprócz witryny wywołania, aby zapewnić odrębność instancji w ramach kompozycji. Jeśli nowy element movie
zostanie dodany na dolnej części listy, funkcja Compose będzie mogła ponownie użyć wystąpień już znajdujących się w kompozycji, ponieważ ich lokalizacja na liście nie uległa zmianie i dlatego dane wejściowe movie
są w tych instancjach takie same.
Rysunek 4. Symbol MoviesScreen
w kompozycji, gdy na dole listy zostanie dodany nowy element. MovieOverview
kompozycji w Kompozycji można wykorzystać ponownie. Ten sam kolor w MovieOverview
oznacza, że funkcja kompozycyjna nie została ponownie skomponowana.
Jeśli jednak lista movies
zmieni się przez dodanie jej na górę lub środkową pozycję, usunięcie lub zmianę kolejności elementów, spowoduje to zmianę kompozycji wszystkich wywołań MovieOverview
, których parametr wejściowy zmienił pozycję na liście. Jest to niezwykle ważne, jeśli na przykład MovieOverview
pobiera obraz z filmu za pomocą efektu ubocznego. Jeśli zmiana kompozycji nastąpi w czasie, gdy efekt jest w toku, zostanie anulowany i rozpocznie się od nowa.
@Composable fun MovieOverview(movie: Movie) { Column { // Side effect explained later in the docs. If MovieOverview // recomposes, while fetching the image is in progress, // it is cancelled and restarted. val image = loadNetworkImage(movie.url) MovieHeader(image) /* ... */ } }
Rysunek 5. Prezentacja właściwości MoviesScreen
w kompozycji, gdy do listy dodano nowy element. MovieOverview
komponentów kompozycyjnych nie można używać ponownie, a wszystkie efekty uboczne zostaną uruchomione ponownie. Inny kolor w MovieOverview
oznacza,
że funkcja kompozycyjna została skomponowana ponownie.
Idealnie chcemy traktować tożsamość instancji MovieOverview
jako połączoną z tożsamością elementu movie
, która jest do niej przekazywana. Gdy zmieniamy kolejność filmów na liście, najlepiej byłoby to zrobić w podobny sposób w instancji w drzewie kompozycji, zamiast ponownie komponować każdy element MovieOverview
z inną instancją filmu. Tworzenie pozwala określić w środowisku wykonawczym, jakich wartości chcesz użyć do identyfikacji danej części drzewa: elementu kompozycyjnego key
.
Dzięki pakowaniu bloku kodu wywołaniem klucza kompozycyjnego z co najmniej 1 przekazaną wartością wartości te zostaną połączone, aby można było zidentyfikować tę instancję w kompozycji. Wartość właściwości key
nie musi być unikalna globalnie, a jedynie musi być unikalna wśród wywołań funkcji kompozycyjnych w witrynie wywołania. W tym przykładzie każdy obiekt movie
musi mieć element key
, który jest unikalny wśród elementów movies
. Może też współdzielić ten element key
z jakimś innym elementem kompozycyjnym w innym miejscu w aplikacji.
@Composable fun MoviesScreenWithKey(movies: List<Movie>) { Column { for (movie in movies) { key(movie.id) { // Unique ID for this movie MovieOverview(movie) } } } }
Dzięki temu, nawet jeśli elementy na liście ulegną zmianie, funkcja tworzenia rozpoznaje pojedyncze wywołania funkcji MovieOverview
i może ich używać ponownie.
Rysunek 6. Prezentacja właściwości MoviesScreen
w kompozycji, gdy do listy dodano nowy element. Elementy kompozycyjne MovieOverview
mają unikalne klucze, dlatego funkcja Compose rozpoznaje, które instancje MovieOverview
się nie zmieniły, i może ich ponownie użyć. Ich efekty uboczne będą nadal wykonywane.
Niektóre funkcje kompozycyjne mają wbudowaną obsługę funkcji kompozycyjnej key
. Na przykład LazyColumn
umożliwia określenie niestandardowego parametru key
w DSL items
.
@Composable fun MoviesScreenLazy(movies: List<Movie>) { LazyColumn { items(movies, key = { movie -> movie.id }) { movie -> MovieOverview(movie) } } }
Pomijanie, jeśli dane wejściowe się nie zmieniły
Podczas zmiany kompozycji niektóre kwalifikujące się funkcje kompozycyjne mogą zostać całkowicie pominięte, jeśli ich dane wejściowe nie zmieniły się od poprzedniej kompozycji.
Funkcja kompozycyjna może zostać pominięta, chyba że:
- Funkcja zwraca typ inny niż
Unit
- Funkcja jest opatrzona adnotacjami
@NonRestartableComposable
lub@NonSkippableComposable
- Wymagany parametr ma niestabilny typ
Istnieje eksperymentalny tryb kompilatora Strong Pomiń, który łagodzi ostatnie wymaganie.
Aby dany typ został uznany za stabilny, musi być zgodny z tą umową:
- Wynik funkcji
equals
w przypadku 2 instancji będzie zawsze taki sam w przypadku tych samych 2 instancji. - Jeśli zmieni się właściwość publiczna danego typu, zostanie o tym powiadomiona funkcja Kompozycja.
- Wszystkie typy właściwości publicznych również są stabilne.
Ta umowa obejmuje kilka ważnych, typowych typów, które kompilator wiadomości będzie traktować jako stabilne, mimo że nie są wyraźnie oznaczone jako stabilne za pomocą adnotacji @Stable
:
- Wszystkie typy wartości podstawowych:
Boolean
,Int
,Long
,Float
,Char
itd. - Strings
- Wszystkie typy funkcji (lambda)
Wszystkie te typy mogą być zgodne z umową stabilną, ponieważ są stałe. Typy stałe nigdy się nie zmieniają, więc nigdy nie muszą powiadamiać o zmianie.
Jednym z ważnych typów, który jest stabilny, ale zmienny, jest typ MutableState
w komponencie. Jeśli wartość jest przechowywana w elemencie MutableState
, ogólny obiekt stanu jest uznawany za stabilny, ponieważ funkcja Utwórz jest powiadamiana o wszelkich zmianach we właściwości .value
obiektu State
.
Gdy wszystkie typy przekazywane jako parametry do funkcji kompozycyjnej są stabilne, wartości parametrów są porównywane pod kątem równości na podstawie pozycji elementu kompozycyjnego w drzewie interfejsu. Zmiana kompozycji jest pomijana, jeśli wszystkie wartości pozostały niezmienione od poprzedniego wywołania.
Funkcja tworzenia jest uznawana za stabilną tylko wtedy, gdy jest w stanie to udowodnić. Na przykład interfejs jest zwykle traktowany jako niestabilny, a typy ze zmiennymi właściwościami publicznymi, których implementacja mogłaby być stała, również nie są stabilne.
Jeśli w przypadku tworzenia wiadomości nie uda się ustalić, że dany typ jest stabilny, ale chcesz zmusić go do traktowania go jako stabilnego, oznacz go za pomocą adnotacji @Stable
.
// Marking the type as stable to favor skipping and smart recompositions. @Stable interface UiState<T : Result<T>> { val value: T? val exception: Throwable? val hasError: Boolean get() = exception != null }
Ponieważ we fragmencie kodu powyżej UiState
jest interfejsem, funkcja tworzenia może zwykle uznać ten typ za niestabilny. Dodając adnotację @Stable
, informujesz Kompozycję, że ten typ jest stabilny, dzięki czemu faworyzujesz inteligentne zmiany kompozycji. Oznacza to również, że jeśli jako typu parametru będzie używany interfejs, funkcja Compose będzie traktować wszystkie jej implementacje jako stabilne.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- State i Jetpack Compose
- Efekty uboczne w oknie tworzenia wiadomości
- Zapisywanie stanu interfejsu użytkownika w momencie tworzenia