A seconda di dove è istruito lo stato e della logica richiesta, puoi utilizzare diverse API per archiviare e ripristinare lo stato della UI. Ogni app usa una combinazione di API per raggiungere questo obiettivo.
Qualsiasi app per Android potrebbe perdere lo stato dell'interfaccia utente a causa di attività o creazione di processi. 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 della configurazione non venga gestita manualmente.
- Decesso del processo avviato dal sistema. L'app è in background e il dispositivo libera risorse (come memoria) che possono essere utilizzate da altri processi.
Conservare lo stato dopo questi eventi è essenziale per un'esperienza utente positiva. La selezione dello stato da mantenere dipende dai flussi di utenti unici della tua app. Come best practice, devi almeno preservare lo stato dell'input utente e della navigazione. Alcuni esempi sono la posizione di scorrimento di un elenco, l'ID dell'elemento su cui l'utente desidera maggiori dettagli, la selezione in corso delle preferenze dell'utente o l'inserimento nei campi di testo.
Questa pagina riassume le API disponibili per l'archiviazione dello stato dell'interfaccia utente in base a dove viene istruito lo stato e alla logica che ne ha bisogno.
Logica UI
Se il tuo stato è visualizzato nell'interfaccia utente, in funzioni componibili o classi di titolari di stato semplici con l'ambito della composizione, puoi utilizzare rememberSaveable
per mantenere lo stato in tutte le attività e i processi di creazione.
Nello snippet seguente, rememberSaveable
viene utilizzato per memorizzare un singolo stato dell'elemento 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 della chat è compressa o espansa.
rememberSaveable
archivia lo stato dell'elemento UI in un Bundle
tramite il meccanismo dello stato dell'istanza salvato.
È in grado di archiviare automaticamente i tipi primitivi nel bundle. Se il tuo stato è di un tipo non primitivo, come una classe di dati, puoi utilizzare diversi meccanismi di archiviazione, ad esempio l'annotazione Parcelize
, l'API Compose come listSaver
e mapSaver
o l'implementazione di una classe di salvaschermo personalizzata che estende la classe di runtime Saver
di Compose. Per saperne di più su questi metodi, consulta la documentazione relativa alle modalità di archiviazione dello stato.
Nel seguente snippet, l'API rememberLazyListState
Compose archivia LazyListState
, ovvero lo stato di scorrimento di un
LazyColumn
o LazyRow
utilizzando rememberSaveable
. Utilizza LazyListState.Saver
, ovvero un salvaschermo 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 della 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
usa un elemento Bundle
per archiviare lo stato dell'interfaccia utente, che è condiviso da altre API che vi scrivono, ad esempio le chiamate onSaveInstanceState()
nella tua attività. Tuttavia, la dimensione di questo Bundle
è limitata e l'archiviazione di oggetti di grandi dimensioni potrebbe comportare eccezioni TransactionTooLarge
in fase di runtime. Questo
può essere particolarmente problematico in singole app Activity
in cui lo stesso
Bundle
viene utilizzato nell'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, ad esempio ID o chiavi, e utilizzali per delegare il ripristino di stati dell'interfaccia utente più complessi ad altri meccanismi, come l'archiviazione permanente.
Le scelte di progettazione dipendono dai casi d'uso specifici della tua app e dal comportamento degli utenti.
Verifica il ripristino dello stato
Puoi verificare che lo stato archiviato con rememberSaveable
negli elementi Scrivi venga ripristinato correttamente quando l'attività o il processo vengono creati di nuovo. Esistono API specifiche per raggiungere questo obiettivo, come
StateRestorationTester
. Per ulteriori informazioni, consulta la documentazione relativa ai test.
Logica di business
Se lo stato dell'elemento UI viene iscritta a ViewModel
perché è richiesto dalla logica di business, puoi utilizzare le API di ViewModel
.
Uno dei principali vantaggi di usare ViewModel
in un'app Android è che
gestisce senza costi le modifiche alla configurazione. Quando viene apportata una modifica alla configurazione e l'attività viene eliminata e ricreata, lo stato dell'UI sollevato in ViewModel
viene conservato in memoria. Al termine della creazione, la vecchia istanza ViewModel
viene collegata alla nuova istanza dell'attività.
Tuttavia, un'istanza ViewModel
non sopravvive al decesso del processo avviato dal sistema.
Affinché lo stato dell'interfaccia utente rimanga invariato, 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'interfaccia utente, quindi
devi utilizzarlo solo per archiviare lo stato degli elementi UI semplice.
Lo stato dell'interfaccia utente della schermata, generato dall'applicazione di regole aziendali e dall'accesso a livelli dell'applicazione diversi dall'interfaccia utente, non deve essere archiviato in SavedStateHandle
a causa delle sue potenziali complessità e dimensioni. Puoi utilizzare diversi meccanismi per archiviare dati complessi o di grandi dimensioni, come l'archiviazione permanente locale. Dopo la ricreazione di un processo, la schermata viene ricreata con lo stato temporaneo ripristinato che era stato archiviato in SavedStateHandle
(se presente) e lo stato dell'interfaccia utente della schermata viene nuovamente prodotto dal livello dati.
SavedStateHandle
API
SavedStateHandle
ha API diverse per archiviare lo stato degli elementi UI, in particolare:
Scrivi State |
saveable() |
---|---|
StateFlow |
getStateFlow() |
Scrivi State
Usa l'API saveable
di SavedStateHandle
per leggere e scrivere lo stato degli elementi UI come MutableState
, in modo che sopravviva alla ricreazione delle attività e dei processi con una configurazione minima del codice.
L'API saveable
supporta immediatamente i tipi primitivi e riceve un parametro
stateSaver
per utilizzare i salvaschermo personalizzati, proprio come rememberSaveable()
.
Nel seguente snippet, message
archivia i tipi di input utente 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 relativa a SavedStateHandle
.
StateFlow
Utilizza getStateFlow()
per archiviare lo stato degli elementi UI e utilizzarlo come un flusso da SavedStateHandle
. 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 il valore più recente.
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 chiamato 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 il valore
più recente archiviato nella chiave. filteredChannels
ha effettuato l'iscrizione al flusso
per applicare i filtri del canale.
Per ulteriori informazioni sull'API getStateFlow()
, consulta la documentazione relativa a SavedStateHandle
.
Riepilogo
La tabella seguente riassume le API trattate in questa sezione e quando utilizzarle per salvare lo stato dell'interfaccia utente:
Evento | Logica UI | Logica di business in un ViewModel |
---|---|---|
Modifiche alla configurazione | rememberSaveable |
Automatico |
Fine del processo avviato dal sistema | rememberSaveable |
SavedStateHandle |
L'API da utilizzare dipende da dove è mantenuto lo stato e dalla logica che richiede. Per lo stato utilizzato nella logica UI, utilizza rememberSaveable
. Per lo stato utilizzato nella logica di business, se lo mantieni in un ViewModel
, salvalo utilizzando SavedStateHandle
.
Dovresti utilizzare le API bundle (rememberSaveable
e SavedStateHandle
) per archiviare piccole quantità di stato dell'interfaccia utente. Questi dati sono il minimo necessario per ripristinare lo stato precedente dell'interfaccia utente, insieme ad altri meccanismi di archiviazione. Ad esempio, se memorizzi l'ID di un profilo che l'utente stava esaminando 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'interfaccia utente, consulta la documentazione generale sul salvataggio dello stato dell'interfaccia utente e la pagina del livello dati della guida all'architettura.
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Dove istruire lo stato
- State e Jetpack Compose
- Elenchi e griglie