Modulo Saved State per ViewModel Parte di Android Jetpack.
Come indicato in Salvataggio degli stati dell'interfaccia utente, ViewModel oggetti possono gestire
le modifiche alla configurazione, quindi non devi preoccuparti dello stato nelle rotazioni
o in altri casi. Tuttavia, se devi gestire la chiusura del processo avviata dal sistema, potresti utilizzare l'API SavedStateHandle come backup.
Lo stato dell'interfaccia utente viene in genere archiviato o a cui si fa riferimento negli oggetti ViewModel, quindi l'utilizzo di
rememberSaveable in Compose richiede un po' di codice standard che il
modulo Saved State può gestire per te.
Quando utilizzi questo modulo, gli oggetti ViewModel ricevono un SavedStateHandle
oggetto tramite il relativo costruttore. Questo oggetto è una mappa chiave-valore che ti consente di scrivere e recuperare oggetti nello stato salvato. Questi valori persistono dopo che il processo viene chiuso dal sistema e rimangono disponibili tramite lo stesso oggetto.
Lo stato salvato è legato allo stack delle attività. Se lo stack delle attività scompare, scompare anche lo stato salvato. Ciò può verificarsi quando forzi l'arresto di un'app, la rimuovi dal menu Recenti o riavvii il dispositivo. In questi casi, lo stack delle attività scompare e non puoi ripristinare le informazioni nello stato salvato. Negli scenari di chiusura dello stato dell'interfaccia utente avviata dall'utente, lo stato salvato non viene ripristinato. Negli scenari avviati dal sistema, invece, sì.
Per lo stato utilizzato nella logica di business, conservalo in un ViewModel e salvalo utilizzandoSavedStateHandle. Per lo stato utilizzato nella
logica dell'interfaccia utente, utilizza rememberSaveable in Compose.Configurazione
Per utilizzare SavedStateHandle, accettalo come argomento del costruttore del tuo ViewModel.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Puoi quindi recuperare un'istanza del tuo ViewModel all'interno dei tuoi componibili senza alcuna configurazione aggiuntiva. La factory ViewModel predefinita fornisce il SavedStateHandle appropriato al tuo ViewModel.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
Quando fornisci un'istanza ViewModelProvider.Factory personalizzata, puoi
abilitare l'utilizzo di SavedStateHandle utilizzando CreationExtras e il
viewModelFactory DSL.
Utilizzare SavedStateHandle
La classe SavedStateHandle è una mappa chiave-valore che ti consente di scrivere e
recuperare dati nello stato salvato tramite i metodi set() e
get().
Utilizzando SavedStateHandle, il valore della query viene mantenuto durante l'interruzione del processo, assicurando che l'utente veda lo stesso insieme di dati filtrati prima e dopo la ricreazione senza che l'attività o il frammento debbano salvare, ripristinare e inoltrare manualmente il valore al ViewModel.
SavedStateHandle salva i dati scritti solo quando l'
host Activity viene arrestata (ad esempio quando l'app viene inviata in background).
Le scritture in SavedStateHandle mentre l'Activity è arrestata non vengono salvate a meno che l'Activity non riceva onStart seguito di nuovo da onStop (ad esempio quando l'app viene portata in primo piano e poi di nuovo in background).SavedStateHandle ha anche altri metodi che potresti aspettarti quando interagisci con una mappa chiave-valore:
contains(String key)- Controlla se esiste un valore per la chiave specificata.remove(String key)- Rimuove il valore per la chiave specificata.keys()- Restituisce tutte le chiavi contenute inSavedStateHandle.
Inoltre, puoi recuperare i valori da SavedStateHandle utilizzando un contenitore di dati osservabile. L'elenco dei tipi supportati include i seguenti:
get() e set() di base. In Compose, lo stato viene acquisito solo quando l'applicazione passa in background. Ciò significa che, sebbene tu possa continuare ad aggiornare i contenitori di dati osservabili da un SavedStateHandle mentre l'app è in background, tutti gli aggiornamenti dello stato potrebbero andare persi se il processo dell'app viene chiuso prima di tornare in primo piano.StateFlow
Puoi recuperare i valori da SavedStateHandle racchiusi in un oggetto osservabile StateFlow. A seconda che tu debba modificare direttamente il valore, puoi scegliere tra un flusso di sola lettura o modificabile:
getStateFlow(): utilizzalo se devi solo leggere lo stato. Quando aggiorni il valore della chiave in un altro punto diSavedStateHandle, StateFlow riceve il nuovo valore. È l'ideale quando vuoi esporre un flusso di sola lettura e trasformarlo utilizzando gli operatori Flow.getMutableStateFlow(): utilizzalo se hai bisogno dell'accesso in lettura e scrittura. L'aggiornamento di.valuedelMutableStateFlowrestituito aggiorna automaticamente l'oggettoSavedStateHandlesottostante, evitando di dover impostare manualmente la chiave.
Molto spesso, questi valori vengono aggiornati a causa delle interazioni dell'utente, ad esempio l'inserimento di una query per filtrare un elenco di dati.
getMutableStateFlow su SavedStateHandle è stato aggiunto nella
versione 2.9.0 del ciclo di vita.class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
Supporto per la serializzazione KotlinX
Per uno stato dell'interfaccia utente complesso, puoi utilizzare il delegato della proprietà saved insieme alla serializzazione KotlinX. Questo delegato ti consente di rendere persistenti le classi di dati @Serializable personalizzate direttamente in SavedStateHandle. In questo modo, lo stato di ViewModel viene mantenuto durante l'interruzione del processo, in modo che l'interfaccia utente di Compose possa ripristinare senza problemi il suo stato al momento della ricreazione.
Per utilizzarlo, annota la classe di dati con @Serializable e utilizza il delegato saved in ViewModel:
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
saved viene valutato in modo lazy. Non chiama l'espressione lambda di inizializzazione né salva nulla in SavedStateHandle finché non si accede alla proprietà per la prima volta.Supporto per lo stato di Compose
Se il tuo stato si basa sulle API Saver di Compose anziché sulla serializzazione KotlinX, l'artefatto lifecycle-viewmodel-compose fornisce il delegato
saveable. Ciò consente l'interoperabilità tra
SavedStateHandle e Saver di Compose, in modo che qualsiasi State che puoi
salvare tramite rememberSaveable con un Saver personalizzato possa essere salvato anche con
SavedStateHandle.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Tipi supportati
I dati mantenuti all'interno di un SavedStateHandle vengono salvati e ripristinati come un Bundle,
insieme al resto di savedInstanceState per la tua app.
Tipi supportati direttamente
Per impostazione predefinita, puoi chiamare set() e get() su un SavedStateHandle per gli stessi tipi di dati di un Bundle, come mostrato di seguito:
| Supporto per tipo/classe | Supporto per array |
double |
double[] |
int |
int[] |
long |
long[] |
String |
String[] |
byte |
byte[] |
char |
char[] |
CharSequence |
CharSequence[] |
float |
float[] |
Parcelable |
Parcelable[] |
Serializable |
Serializable[] |
short |
short[] |
SparseArray |
|
Binder |
|
Bundle |
|
ArrayList |
|
Size (only in API 21+) |
|
SizeF (only in API 21+) |
Se la classe non estende una di quelle nell'elenco precedente, valuta la possibilità di rendere la
classe parcelable aggiungendo l'annotazione Kotlin @Parcelize o
implementando Parcelable direttamente.
Salvare classi non parcelable
Se una classe non implementa Parcelable o Serializable e non può essere modificata per implementare una di queste interfacce, non è possibile salvare direttamente un'istanza di quella classe in un SavedStateHandle.
A partire da Lifecycle 2.3.0-alpha03, SavedStateHandle ti consente di salvare
qualsiasi oggetto fornendo la tua logica per salvare e ripristinare l'oggetto come
un Bundle utilizzando il metodo setSavedStateProvider().
SavedStateRegistry.SavedStateProvider è un'interfaccia che definisce un
singolo metodo saveState() che restituisce un Bundle contenente lo stato
che vuoi salvare. Quando SavedStateHandle è pronto per salvare il suo stato, chiama saveState() per recuperare il Bundle da SavedStateProvider e salva il Bundle per la chiave associata.
Considera un esempio di un'app che richiede un'immagine dall'app Fotocamera tramite l'intent ACTION_IMAGE_CAPTURE, passando un file temporaneo in cui la fotocamera deve archiviare l'immagine. TempFileViewModel incapsula la logica per la creazione di questo file temporaneo.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Per assicurarsi che il file temporaneo non vada perso se il processo dell'attività viene chiuso e ripristinato in un secondo momento, TempFileViewModel può utilizzare SavedStateHandle per rendere persistenti i dati. Per consentire a TempFileViewModel di salvare i dati, implementa
SavedStateProvider e impostalo come provider su SavedStateHandle di
ViewModel:
private fun File.saveTempFile() = bundleOf("path", absolutePath) class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Per ripristinare i dati File quando l'utente torna, recupera il Bundle temp_file da SavedStateHandle. Si tratta dello stesso Bundle fornito da saveTempFile() che contiene il percorso assoluto. Il percorso assoluto può essere utilizzato per creare un nuovo File.
private fun File.saveTempFile() = bundleOf("path", absolutePath) private fun Bundle.restoreTempFile() = if (containsKey("path")) { File(getString("path")) } else { null } class TempFileViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { private var tempFile: File? = null init { val tempFileBundle = savedStateHandle.get<Bundle>("temp_file") if (tempFileBundle != null) { tempFile = tempFileBundle.restoreTempFile() } savedStateHandle.setSavedStateProvider("temp_file") { // saveState() if (tempFile != null) { tempFile.saveTempFile() } else { Bundle() } } } fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
SavedStateHandle nei test
Per testare un ViewModel che accetta un SavedStateHandle come dipendenza, crea una nuova istanza di SavedStateHandle con i valori di test richiesti e passala all'istanza ViewModel che stai testando.
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Risorse aggiuntive
Per ulteriori informazioni sul modulo Saved State per ViewModel, consulta le seguenti risorse.
Codelab
Contenuti delle visualizzazioni
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Salvare gli stati dell'interfaccia utente
- Utilizzare oggetti di dati osservabili
- Creare ViewModel con dipendenze