W zależności od tego, gdzie jest przenoszony stan i jaka jest wymagana logika, możesz używać różnych interfejsów API do przechowywania i przywracania stanu interfejsu. Każda aplikacja używa kombinacji interfejsów API, aby jak najlepiej to osiągnąć.
Każda aplikacja na Androida może utracić swój stan interfejsu z powodu ponownego utworzenia aktywności lub procesu. Utrata stanu może nastąpić z powodu tych zdarzeń:
- Zmiany konfiguracji. Aktywność jest niszczona i ponownie tworzona, chyba że zmiana konfiguracji jest obsługiwana ręcznie.
- Zakończenie procesu zainicjowane przez system. Aplikacja działa w tle, a urządzenie zwalnia zasoby (np. pamięć), aby mogły być używane przez inne procesy.
Zachowanie stanu po tych zdarzeniach jest niezbędne do zapewnienia pozytywnego wrażenia użytkownika. Wybór stanu do utrwalenia zależy od unikalnych ścieżek użytkownika w aplikacji. Zgodnie ze sprawdzoną metodą należy zachować co najmniej dane wejściowe użytkownika i stan związany z nawigacją. Przykłady to pozycja przewijania listy, identyfikator elementu, o którym użytkownik chce dowiedzieć się więcej, trwający wybór preferencji użytkownika lub dane wejściowe w polach tekstowych.
Na tej stronie znajdziesz podsumowanie interfejsów API dostępnych do przechowywania stanu interfejsu w zależności od tego, gdzie jest przenoszony stan i jaka logika jest wymagana.
Logika interfejsu
Jeśli stan jest przenoszony w interfejsie, w funkcjach kompozycyjnych lub w zwykłych
klasach przechowujących stan w zakresie kompozycji, możesz użyć
rememberSaveable, aby zachować stan podczas ponownego tworzenia aktywności i procesu.
W tym fragmencie kodu funkcja rememberSaveable służy do przechowywania stanu pojedynczego elementu interfejsu typu boolean:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails to zmienna typu boolean, która przechowuje informacje o tym, czy dymek czatu jest zwinięty czy rozwinięty.
rememberSaveable przechowuje stan elementu interfejsu w Bundle za pomocą
mechanizmu zapisanego stanu instancji.
Może automatycznie przechowywać w pakiecie typy podstawowe. Jeśli stan
jest przechowywany w typie, który nie jest podstawowy, np. w klasie danych, możesz użyć
różnych mechanizmów przechowywania, takich jak adnotacja Parcelize,
interfejsy Compose API, np. listSaver i mapSaver, lub zaimplementować
niestandardową klasę zapisującą, która rozszerza klasę Saver środowiska wykonawczego Compose. Więcej informacji o tych metodach znajdziesz w dokumentacji Sposoby
przechowywania stanu.
W tym fragmencie kodu rememberLazyListState Compose
API przechowuje LazyListState, który składa się ze stanu przewijania elementu
LazyColumn lub LazyRow, za pomocą funkcji rememberSaveable. Używa on funkcji
LazyListState.Saver, czyli niestandardowej funkcji zapisującej, która może
przechowywać i przywracać stan przewijania. Po ponownym utworzeniu aktywności lub procesu (np. po zmianie konfiguracji, takiej jak zmiana orientacji urządzenia) stan przewijania jest zachowywany.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Sprawdzona metoda
rememberSaveable używa Bundle do przechowywania stanu interfejsu, który jest współdzielony przez
inne interfejsy API, które również zapisują w nim dane, np. wywołania onSaveInstanceState() w
aktywności. Rozmiar tego Bundle jest jednak ograniczony, a przechowywanie dużych
obiektów może prowadzić do TransactionTooLarge wyjątków w czasie działania. Może to być szczególnie problematyczne w aplikacjach z jedną Activity, w których ten sam Bundle jest używany w całej aplikacji.
Aby uniknąć tego typu awarii, nie należy przechowywać w pakiecie dużych złożonych obiektów ani list obiektów.
Zamiast tego przechowuj minimalny wymagany stan, np. identyfikatory lub klucze, i używaj ich do delegowania przywracania bardziej złożonego stanu interfejsu do innych mechanizmów, np. do trwałego przechowywania.
Te decyzje projektowe zależą od konkretnych przypadków użycia w aplikacji i od tego, jak użytkownicy oczekują jej działania.
Sprawdzanie przywracania stanu
Możesz sprawdzić, czy stan przechowywany za pomocą rememberSaveable w elementach Compose jest prawidłowo przywracany po ponownym utworzeniu aktywności lub procesu. Służą do tego konkretne interfejsy API, np.
StateRestorationTester. Więcej informacji znajdziesz w dokumentacji Testowanie.
Logika biznesowa
Jeśli stan elementu interfejsu jest przenoszony do ViewModel, ponieważ jest
wymagany przez logikę biznesową, możesz używać interfejsów API ViewModel.
Jedną z głównych zalet używania ViewModel w aplikacji na Androida jest to, że bezpłatnie obsługuje zmiany konfiguracji. Gdy nastąpi zmiana konfiguracji, a aktywność zostanie zniszczona i ponownie utworzona, stan interfejsu przeniesiony do ViewModel jest przechowywany w pamięci. Po ponownym utworzeniu stara instancja ViewModel jest dołączana do nowej instancji aktywności.
Instancja ViewModel nie przetrwa jednak zakończenia procesu zainicjowanego przez system.
Aby stan interfejsu przetrwał, użyj modułu Saved State for
ViewModel, który zawiera interfejs SavedStateHandle API.
Sprawdzona metoda
SavedStateHandle używa też mechanizmu Bundle do przechowywania stanu interfejsu, dlatego
należy go używać tylko do przechowywania prostego stanu elementu interfejsu.
Stan interfejsu ekranu, który jest tworzony przez stosowanie reguł biznesowych i dostęp do
warstw aplikacji innych niż interfejs, nie powinien być przechowywany w
SavedStateHandle ze względu na jego potencjalną złożoność i rozmiar. Do przechowywania złożonych lub dużych danych możesz używać
różnych mechanizmów, np. lokalnego trwałego
przechowywania. Po ponownym utworzeniu procesu ekran jest ponownie tworzony z przywróconym stanem przejściowym, który był przechowywany w SavedStateHandle (jeśli taki stan istniał), a stan interfejsu ekranu jest ponownie tworzony na podstawie warstwy danych.
Interfejsy API SavedStateHandle
SavedStateHandle ma różne interfejsy API do przechowywania stanu elementu interfejsu, w szczególności:
Compose State |
saveable() |
|---|---|
StateFlow |
getStateFlow() |
Compose State
Użyj interfejsu saveable API SavedStateHandle, aby odczytywać i zapisywać stan elementu interfejsu jako MutableState, dzięki czemu przetrwa on ponowne utworzenie aktywności i procesu przy minimalnej konfiguracji kodu.
Interfejs saveable API obsługuje typy podstawowe od razu po wyjęciu z pudełka i otrzymuje parametr stateSaver, aby używać niestandardowych funkcji zapisujących, tak jak rememberSaveable().
W tym fragmencie kodu message przechowuje dane wejściowe użytkownika wpisane w TextField:
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
Więcej informacji o używaniu interfejsu saveable API znajdziesz w dokumentacji SavedStateHandle.
StateFlow
Użyj getStateFlow(), aby przechowywać stan elementu interfejsu i używać go jako przepływu
z SavedStateHandle. The StateFlow is read-only,
and the API requires you to specify a key so you can replace the flow to
emit a new value. Za pomocą skonfigurowanego klucza możesz pobrać StateFlow i zebrać najnowszą wartość.
W tym fragmencie kodu savedFilterType to zmienna StateFlow, która przechowuje typ filtra zastosowany do listy kanałów czatu w aplikacji do czatu:
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
Za każdym razem, gdy użytkownik wybierze nowy typ filtra, wywoływana jest funkcja setFiltering. Spowoduje to zapisanie nowej wartości w SavedStateHandle przechowywanej pod kluczem _CHANNEL_FILTER_SAVED_STATE_KEY_. savedFilterType to przepływ emitujący najnowszą wartość przechowywaną pod kluczem. filteredChannels jest subskrybowany w przepływie, aby filtrować kanały.
Więcej informacji o interfejsie
getStateFlow() API znajdziesz w dokumentacji SavedStateHandle.
Podsumowanie
W tabeli poniżej znajdziesz podsumowanie interfejsów API omówionych w tej sekcji oraz informacje o tym, kiedy należy używać każdego z nich do zapisywania stanu interfejsu:
| Wydarzenie | Logika interfejsu | Logika biznesowa w ViewModel |
|---|---|---|
| Zmiany konfiguracji | rememberSaveable |
Automatycznie |
| Śmierć procesu zainicjowana przez system | rememberSaveable |
SavedStateHandle |
Interfejs API, którego należy użyć, zależy od tego, gdzie jest przechowywany stan i jaka jest wymagana logika. W przypadku stanu używanego w logice interfejsu użyj funkcji rememberSaveable. W przypadku
stanu używanego w logice biznesowej, jeśli przechowujesz go w ViewModel,
zapisz go za pomocą SavedStateHandle.
Do przechowywania niewielkich ilości stanu interfejsu należy używać interfejsów API pakietu (rememberSaveable i SavedStateHandle). Te dane są minimalną ilością informacji niezbędnych do przywrócenia interfejsu do poprzedniego stanu wraz z innymi mechanizmami przechowywania. Jeśli na przykład w pakiecie przechowujesz identyfikator profilu, który użytkownik przeglądał, możesz pobrać z warstwy danych duże ilości danych, np. szczegóły profilu.
Więcej informacji o różnych sposobach zapisywania stanu interfejsu znajdziesz w ogólnej dokumentacji Zapisywanie stanu interfejsu oraz na stronie warstwy danych w przewodniku po architekturze.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Gdzie przenieść stan
- Stan i Jetpack Compose
- Listy i siatki