W zależności od tego, gdzie jest przenoszony stan i od wymaganych zasad logicznych, możesz przechowywać i przywracać stan interfejsu za pomocą różnych interfejsów API. Aby to osiągnąć, każda aplikacja korzysta z kombinacji interfejsów API.
Każda aplikacja na Androida może stracić stan interfejsu z powodu aktywności lub odtworzenia procesu. Utrata stanu może być spowodowana tymi przyczynami:
- Zmiany konfiguracji. Aktywność zostanie zniszczona i odtworzona, chyba że zmiana konfiguracji zostanie obsługiwana ręcznie.
- Przerwanie procesu inicjowanego przez system. Aplikacja działa w tle, a urządzenie zwalnia zasoby (np. pamięć) do wykorzystania przez inne procesy.
Zachowanie stanu po wystąpieniu tych zdarzeń jest niezbędne dla wygody użytkowników. Wybór stanu do zachowania zależy od procesów podejmowanych przez unikalnych użytkowników aplikacji. Sprawdzoną metodą jest zachowanie przynajmniej stanu danych wejściowych użytkownika i stanu związanych z nawigacją. Może to być na przykład pozycja przewijania listy, identyfikator elementu, o którym użytkownik chce uzyskać więcej informacji, trwający wybór preferencji użytkownika lub dane wprowadzone w polach tekstowych.
Na tej stronie znajdziesz podsumowanie interfejsów API dostępnych do przechowywania stanu interfejsu użytkownika w zależności od tego, do którego stanu zostanie przeniesiony stan i od logiki, która go wymaga.
Logika interfejsu
Jeśli Twój stan jest pobierany w interfejsie (w funkcjach kompozycyjnych lub klasach posiadaczy zwykłych stanów do kompozycji, możesz użyć rememberSaveable
do zachowywania stanu w ramach wszystkich działań i odtwarzania procesów).
W tym fragmencie kodu rememberSaveable
służy do przechowywania stanu pojedynczego elementu interfejsu z wartością logiczną:
@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 logiczna, która przechowuje, gdy dymek czatu jest zwinięty lub rozwinięty.
rememberSaveable
przechowuje stan elementu interfejsu w Bundle
za pomocą mechanizmu zapisanego stanu instancji.
Może automatycznie zapisywać w pakiecie typy podstawowe. Jeśli stan jest utrzymywany w typie, który nie jest prymitywny, np. klasa danych, możesz użyć innych mechanizmów przechowywania, np. za pomocą adnotacji Parcelize
lub interfejsów API tworzenia wiadomości, takich jak listSaver
i mapSaver
, lub zaimplementowanych niestandardowych klas oszczędzania rozszerzających klasę środowiska wykonawczego Saver
. Więcej informacji o tych metodach znajdziesz w dokumentacji sposobów przechowywania informacji o stanie.
W poniższym fragmencie interfejs rememberLazyListState
Compose API przechowuje zbiór LazyListState
, który składa się ze stanu przewijania LazyColumn
lub LazyRow
z wykorzystaniem rememberSaveable
. Używa ona LazyListState.Saver
– niestandardowego wygaszacza, który może przechowywać i przywracać stan przewijania. Po odtworzeniu działania lub procesu (np. po zmianie konfiguracji, np. zmianie 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 udostępniany przez inne interfejsy API, które również w nim zapisują, np. wywołania onSaveInstanceState()
w Twojej aktywności. Rozmiar tego obiektu Bundle
jest jednak ograniczony, a przechowywanie dużych obiektów może spowodować utworzenie wyjątków w czasie działania (TransactionTooLarge
). Może to być szczególnie problematyczne w przypadku pojedynczych aplikacji typu Activity
, w których w całej aplikacji używa się tego samego parametru Bundle
.
Aby uniknąć awarii tego typu, nie przechowuj w pakiecie dużych złożonych obiektów ani list.
Zamiast tego zapisz 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 pamięci trwałej.
Wybrane opcje projektowe zależą od konkretnych przypadków użycia aplikacji i oczekiwania użytkowników.
Sprawdź przywrócenie stanu
Możesz sprawdzić, czy stan zapisany w rememberSaveable
w elementach tworzenia wiadomości jest prawidłowo przywracany po utworzeniu działania lub procesu. Istnieją specjalne interfejsy API, które pozwalają to zrobić, np. StateRestorationTester
. Więcej informacji znajdziesz w dokumentacji testowania.
Logika biznesowa
Jeśli stan elementu interfejsu jest przenoszony do ViewModel
, ponieważ jest to wymagane przez logikę biznesową, możesz używać interfejsów API ViewModel
.
Jedną z głównych zalet korzystania z ViewModel
w aplikacji na Androida jest to, że umożliwia on bezpłatne wprowadzanie zmian w konfiguracji. W przypadku zmiany konfiguracji, a następnie zniszczeniu działania i jego odtworzeniu, stan interfejsu przeniesiony do elementu ViewModel
jest zachowywany w pamięci. Po odtworzeniu stara instancja ViewModel
jest dołączona do nowej instancji aktywności.
Instancja ViewModel
nie przetrwa jednak śmierci procesu inicjowanego przez system.
Aby stan interfejsu użytkownika przetrwał ten okres, użyj modułu Zapisanego stanu dla obiektu ViewModel, który zawiera interfejs API SavedStateHandle
.
Sprawdzona metoda
SavedStateHandle
wykorzystuje również mechanizm Bundle
do przechowywania stanu interfejsu, więc należy go używać tylko do przechowywania prostego stanu elementu interfejsu.
Stan interfejsu ekranu, który powstaje przez stosowanie reguł biznesowych i uzyskiwanie dostępu do warstw aplikacji innych niż UI, 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. lokalnej pamięci trwałej. Po odtworzeniu procesu ekran jest odtworzony z przywróconym stanem przejściowym, który został zapisany w funkcji SavedStateHandle
(jeśli występuje), a stan interfejsu ekranu jest ponownie generowany z warstwy danych.
SavedStateHandle
interfejsów API
SavedStateHandle
ma różne interfejsy API do zapisywania stanu elementów interfejsu, a w szczególności:
Utwórz State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Utwórz State
Używaj interfejsu saveable
API w SavedStateHandle
, aby odczytywać i zapisywać stan elementu interfejsu o stanie MutableState
, aby zachować aktywność i przetwarzać odtwarzanie przy minimalnej konfiguracji kodu.
Interfejs saveable
API obsługuje od razu typy podstawowe i otrzymuje parametr stateSaver
służący do korzystania z niestandardowych wygaszaczy (tak jak rememberSaveable()
).
W tym fragmencie message
zapisuje dane wejściowe użytkownika w elemencie 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 korzystaniu z interfejsu API saveable
znajdziesz w dokumentacji SavedStateHandle
.
StateFlow
Używaj getStateFlow()
, aby przechowywać stan elementu interfejsu i wykorzystywać go jako przepływ z SavedStateHandle
. StateFlow
jest tylko do odczytu, a interfejs API wymaga określenia klucza, aby można było zastąpić przepływ w celu wyemitowania nowej wartości. Za pomocą skonfigurowanego klucza możesz pobrać StateFlow
i pobrać najnowszą wartość.
W tym fragmencie kodu savedFilterType
to zmienna StateFlow
, która przechowuje typ filtra zastosowany do listy kanałów czatu w aplikacji do obsługi 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ływane jest setFiltering
. Spowoduje to zapisanie nowej wartości w elemencie SavedStateHandle
zapisanej z kluczem _CHANNEL_FILTER_SAVED_STATE_KEY_
. savedFilterType
to przepływ, który przesyła najnowszą wartość zapisaną w kluczu. filteredChannels
subskrybuje przepływ, aby móc filtrować kanały.
Więcej informacji o interfejsie API getStateFlow()
znajdziesz w dokumentacji SavedStateHandle
.
Podsumowanie
W tabeli poniżej znajdziesz podsumowanie omówionych w tej sekcji interfejsów API i informacje o tym, kiedy przy użyciu każdego z nich zapisać stan interfejsu:
Wydarzenie | Logika interfejsu | Logika biznesowa w obiekcie ViewModel |
---|---|---|
Zmiany konfiguracji | rememberSaveable |
Automatyczny |
Przerwanie procesu inicjowanego przez system | rememberSaveable |
SavedStateHandle |
Interfejs API, którego należy użyć, zależy od tego, gdzie jest utrzymywany stan i jakie wymaga logiki. Jako stan używany w logice UI użyj rememberSaveable
. W przypadku stanu używanego w logice biznesowej, jeśli przechowujesz go w polu ViewModel
, zapisz go za pomocą atrybutu SavedStateHandle
.
Do przechowywania niewielkich ilości stanu interfejsu użytkownika należy używać interfejsów API pakietu (rememberSaveable
i SavedStateHandle
). Dane te to minimum niezbędne do przywrócenia interfejsu użytkownika do poprzedniego stanu wraz z innymi mechanizmami przechowywania. Jeśli na przykład przechowujesz w pakiecie identyfikator profilu, który oglądał użytkownik, możesz pobierać z warstwy danych bardzo dużo danych, takich jak szczegóły profilu.
Więcej informacji o różnych sposobach zapisywania stanu interfejsu znajdziesz w ogólnej dokumentacji dotyczącej zapisywania stanu interfejsu i na stronie warstwy danych w przewodniku po architekturze.
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Gdzie przenieść stan
- State i Jetpack Compose
- Listy i siatki