A seconda di dove viene sollevato lo stato e della logica richiesta, puoi utilizzare API diverse per archiviare e ripristinare lo stato dell'UI. Ogni app utilizza una combinazione di API per ottenere il risultato migliore.
Qualsiasi app per Android potrebbe perdere il suo stato dell'UI a causa della ricreazione dell'attività o del processo. Questa perdita di stato può verificarsi a causa dei seguenti eventi:
- Modifiche alla configurazione. L'attività viene eliminata e ricreata a meno che la modifica alla configurazione non venga gestita manualmente.
- Interruzione del processo avviata dal sistema. L'app è in background e il dispositivo libera risorse (come la memoria) da utilizzare per altri processi.
Mantenere lo stato dopo questi eventi è essenziale per un'esperienza utente positiva. La selezione dello stato da rendere persistente dipende dai flussi utente unici della tua app. Come best practice, dovresti almeno mantenere lo stato relativo all'input dell'utente e alla navigazione. Esempi includono la posizione di scorrimento di un elenco, l'ID dell'elemento di cui l'utente vuole maggiori dettagli, la selezione in corso delle preferenze dell'utente o l'input nei campi di testo.
Questa pagina riassume le API disponibili per archiviare lo stato dell'UI a seconda di dove viene sollevato lo stato e della logica che lo richiede.
Logica dell'UI
Se lo stato viene sollevato nell'UI, nelle funzioni componibili o nelle classi di contenitori di stato semplici con ambito di composizione, puoi utilizzare rememberSaveable per mantenere lo stato durante la ricreazione dell'attività e del processo.
Nel seguente snippet, rememberSaveable viene utilizzato per archiviare lo stato di un singolo elemento dell'UI booleano:
@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 è una variabile booleana che memorizza se la bolla di chat è compressa o espansa.
rememberSaveable archivia lo stato dell'elemento dell'UI in un Bundle tramite il
meccanismo dello stato dell'istanza salvata.
È in grado di archiviare automaticamente i tipi primitivi nel bundle. Se lo stato
è contenuto in un tipo non primitivo, ad esempio una classe di dati, puoi utilizzare
meccanismi di archiviazione diversi, ad esempio l'annotazione Parcelize,
le API Compose come listSaver e mapSaver o l'implementazione di una
classe di salvataggio personalizzata che estende la classe Saver di runtime di Compose. Per saperne di più su questi metodi, consulta la documentazione Modalità
di archiviazione dello stato.
Nel seguente snippet, l'rememberLazyListState Compose
API archivia LazyListState, che consiste nello stato di scorrimento di un
LazyColumn o LazyRow, utilizzando rememberSaveable. Utilizza un
LazyListState.Saver, ovvero un salvataggio personalizzato in grado di
archiviare e ripristinare lo stato di scorrimento. Dopo la ricreazione di un'attività o di un processo (ad esempio, dopo una modifica alla configurazione come la modifica dell'orientamento del dispositivo), lo stato di scorrimento viene mantenuto.
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
Best practice
rememberSaveable utilizza un Bundle per archiviare lo stato dell'UI, che viene condiviso da
altre API che scrivono anche in esso, come le chiamate onSaveInstanceState() nell'
attività. Tuttavia, la dimensione di questo Bundle è limitata e l'archiviazione di oggetti di grandi
dimensioni potrebbe generare eccezioni TransactionTooLarge in fase di runtime. Questo può essere particolarmente problematico nelle app con una singola Activity in cui lo stesso Bundle viene utilizzato in tutta l'app.
Per evitare questo tipo di arresto anomalo, non devi archiviare oggetti complessi di grandi dimensioni o elenchi di oggetti nel bundle.
Archivia invece lo stato minimo richiesto, come ID o chiavi, e utilizzali per delegare il ripristino di uno stato dell'UI più complesso ad altri meccanismi, come l'archiviazione permanente.
Queste scelte di progettazione dipendono dai casi d'uso specifici della tua app e dal comportamento previsto dagli utenti.
Verificare il ripristino dello stato
Puoi verificare che lo stato archiviato con rememberSaveable negli elementi Compose venga ripristinato correttamente quando l'attività o il processo viene ricreato. Esistono API specifiche per raggiungere questo obiettivo, ad esempio
StateRestorationTester. Per saperne di più, consulta la documentazione sui test.
Logica di business
Se lo stato dell'elemento dell'UI viene sollevato nel ViewModel perché è
richiesto dalla logica di business, puoi utilizzare le API di ViewModel.
Uno dei principali vantaggi dell'utilizzo di un ViewModel nella tua app per Android è che gestisce senza costi le modifiche alla configurazione. Quando si verifica una modifica alla configurazione e l'attività viene eliminata e ricreata, lo stato dell'UI sollevato nel ViewModel viene mantenuto in memoria. Dopo la ricreazione, la vecchia istanza di ViewModel viene collegata alla nuova istanza di attività.
Tuttavia, un'istanza di ViewModel non sopravvive all'interruzione del processo avviata dal sistema.
Per fare in modo che lo stato dell'UI sopravviva, utilizza il modulo Stato salvato per
ViewModel, che contiene l'API SavedStateHandle.
Best practice
SavedStateHandle utilizza anche il meccanismo Bundle per archiviare lo stato dell'UI, quindi
dovresti utilizzarlo solo per archiviare lo stato semplice dell'elemento dell'UI .
Lo stato dell'UI della schermata, che viene prodotto applicando regole di business e accedendo a
livelli dell'applicazione diversi dall'UI, non deve essere archiviato in
SavedStateHandle a causa della sua potenziale complessità e dimensione. Puoi utilizzare
meccanismi diversi per archiviare dati complessi o di grandi dimensioni, ad esempio l'archiviazione permanente locale
storage. Dopo la ricreazione di un processo, la schermata viene ricreata con lo stato temporaneo ripristinato archiviato in SavedStateHandle (se presente) e lo stato dell'UI della schermata viene prodotto di nuovo dal livello dati.
API SavedStateHandle
SavedStateHandle ha diverse API per archiviare lo stato dell'elemento dell'UI, in particolare:
Compose State |
saveable() |
|---|---|
StateFlow |
getStateFlow() |
State di Compose
Utilizza l'API saveable di SavedStateHandle per leggere e scrivere lo stato dell'elemento dell'UI come MutableState, in modo che sopravviva alla ricreazione dell'attività e del processo con una configurazione del codice minima.
L'API saveable supporta i tipi primitivi out-of-the-box e riceve un parametro stateSaver per utilizzare salvataggi personalizzati, proprio come rememberSaveable().
Nel seguente snippet, message memorizza l'input dell'utente digitato in un 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) } ) }
Per ulteriori informazioni sull'utilizzo dell'API saveable, consulta la documentazione di SavedStateHandle.
StateFlow
Utilizza getStateFlow() per archiviare lo stato dell'elemento dell'UI e utilizzarlo come flusso
da SavedStateHandle. The StateFlow è di sola lettura,
e l'API richiede di specificare una chiave in modo da poter sostituire il flusso per
emettere un nuovo valore. Con la chiave configurata, puoi recuperare StateFlow e raccogliere l'ultimo valore.
Nel seguente snippet, savedFilterType è una variabile StateFlow che memorizza un tipo di filtro applicato a un elenco di canali di chat in un'app di chat:
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 }
Ogni volta che l'utente seleziona un nuovo tipo di filtro, viene chiamata setFiltering. In questo modo viene salvato un nuovo valore in SavedStateHandle archiviato con la chiave _CHANNEL_FILTER_SAVED_STATE_KEY_. savedFilterType è un flusso che emette l'ultimo valore archiviato nella chiave. filteredChannels è sottoscritto al flusso per eseguire il filtraggio dei canali.
Per ulteriori informazioni sull'API
getStateFlow(), consulta la documentazione di SavedStateHandle.
Riepilogo
La seguente tabella riassume le API trattate in questa sezione e quando utilizzare ciascuna per salvare lo stato dell'UI:
| Evento | Logica dell'UI | Logica di business in un ViewModel |
|---|---|---|
| Modifiche alla configurazione | rememberSaveable |
Automatico |
| Interruzione del processo avviata dal sistema | rememberSaveable |
SavedStateHandle |
L'API da utilizzare dipende da dove viene mantenuto lo stato e dalla logica che richiede. Per lo stato utilizzato nella logica dell'UI, utilizza rememberSaveable. Per
lo stato utilizzato nella logica di business, se lo mantieni in un ViewModel,
salvalo utilizzando SavedStateHandle.
Dovresti utilizzare le API del bundle (rememberSaveable e SavedStateHandle) per archiviare piccole quantità di stato dell'UI. Questi dati sono il minimo necessario per ripristinare l'UI al suo stato precedente, insieme ad altri meccanismi di archiviazione. Ad esempio, se memorizzi l'ID di un profilo che l'utente stava visualizzando nel bundle, puoi recuperare dati pesanti, come i dettagli del profilo, dal livello dati.
Per ulteriori informazioni sui diversi modi per salvare lo stato dell'UI, consulta la documentazione generale Salvataggio dello stato dell'UI e la pagina del livello dati della guida all'architettura.
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Dove sollevare lo stato
- Stato e Jetpack Compose
- Elenchi e griglie