Modulo dello stato salvato per ViewModel Componente di Android Jetpack.
Come menzionato in Salvataggio degli stati dell'interfaccia utente, gli oggetti ViewModel
possono gestire le modifiche alla configurazione, quindi non devi preoccuparti dello stato nelle rotazioni o in altri casi. Tuttavia, se devi gestire la disattivazione del processo avviato dal sistema, potresti utilizzare l'API SavedStateHandle
come backup.
In genere lo stato dell'interfaccia utente è archiviato o vi viene fatto riferimento in ViewModel
oggetti e non
in attività, quindi l'utilizzo di onSaveInstanceState()
o rememberSaveable
richiede un
boilerplate che il modulo dello stato salvato
è in grado di gestire.
Quando utilizzi questo modulo, gli oggetti ViewModel
ricevono un oggetto SavedStateHandle
tramite il relativo costruttore. Questo oggetto è una mappa chiave-valore che consente di scrivere e recuperare oggetti da e verso lo stato salvato. Questi valori rimangono disponibili dopo l'interruzione del processo dal sistema e rimangono disponibili tramite lo stesso oggetto.
Lo stato salvato è legato allo stack di attività. Se l'elenco di attività scompare, scompare anche lo stato salvato. Questo può accadere quando si forza l'interruzione di un'app, la si rimuove dal menu Recenti o si riavvia il dispositivo. In questi casi, l'elenco di attività scompare e non è possibile ripristinare le informazioni nello stato salvato. Negli scenari di ignoramento dello stato dell'interfaccia utente avviata dall'utente, lo stato salvato non viene ripristinato. Negli scenari avviati dal sistema, lo è.
Configurazione
A partire dal Frammento 1.2.0
o dalla sua dipendenza transitiva
Attività 1.1.0, puoi accettare
un SavedStateHandle
come argomento del costruttore per il tuo ViewModel
.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Puoi quindi recuperare un'istanza di ViewModel
senza alcuna configurazione aggiuntiva. Il valore di fabbrica predefinito di ViewModel
fornisce il valore SavedStateHandle
appropriato al tuo ViewModel
.
Kotlin
class MainFragment : Fragment() { val vm: SavedStateViewModel by viewModels() ... }
Java
class MainFragment extends Fragment { private SavedStateViewModel vm; public void onViewCreated(@NonNull View view, Bundle savedInstanceState) { vm = new ViewModelProvider(this).get(SavedStateViewModel.class); ... } ... }
Quando fornisci un'istanza ViewModelProvider.Factory
personalizzata, puoi abilitare l'utilizzo di SavedStateHandle
estendendo AbstractSavedStateViewModelFactory
.
Utilizzo di SaveStateHandle
La classe SavedStateHandle
è una mappa chiave-valore che consente di scrivere e recuperare dati da e verso lo stato salvato tramite i metodi set()
e get()
.
Utilizzando SavedStateHandle
, il valore della query viene mantenuto durante l'interruzione del processo, garantendo che l'utente veda lo stesso insieme di dati filtrati prima e dopo la creazione senza che l'attività o il frammento debbano salvare, ripristinare e inoltrare manualmente quel valore al ViewModel
.
SavedStateHandle
offre 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 della chiave specificata.keys()
: restituisce tutte le chiavi contenute all'interno diSavedStateHandle
.
Inoltre, puoi recuperare i valori da SavedStateHandle
utilizzando un titolare di dati osservabile. Di seguito sono elencati i tipi supportati:
Dati in tempo reale
Recupera i valori da SavedStateHandle
che sono aggregati in un elemento
LiveData
osservabile utilizzando
getLiveData()
.
Quando il valore della chiave viene aggiornato, l'oggetto LiveData
riceve il nuovo valore. Nella maggior parte dei casi, il valore viene impostato a causa delle interazioni degli utenti, ad esempio l'inserimento di una query per filtrare un elenco di dati. Questo valore aggiornato può essere utilizzato per
trasformare LiveData
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: LiveData<List<String>> = savedStateHandle.getLiveData<String>("query").switchMap { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle savedStateHandle; public LiveData<List<String>> filteredData; public SavedStateViewModel(SavedStateHandle savedStateHandle) { this.savedStateHandle = savedStateHandle; LiveData<String> queryLiveData = savedStateHandle.getLiveData("query"); filteredData = Transformations.switchMap(queryLiveData, query -> { return repository.getFilteredData(query); }); } public void setQuery(String query) { savedStateHandle.set("query", query); } }
Flusso statale
Recupera i valori da SavedStateHandle
che sono aggregati in un elemento
StateFlow
osservabile utilizzando
getStateFlow()
.
Quando aggiorni il valore della chiave, l'oggetto StateFlow
riceve il nuovo valore. Nella maggior parte dei casi, potresti impostare il valore in base alle interazioni degli utenti, ad esempio l'inserimento di una query per filtrare un elenco di dati. Puoi quindi trasformare questo valore aggiornato utilizzando altri operatori di flusso.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { val filteredData: StateFlow<List<String>> = savedStateHandle.getStateFlow<String>("query") .flatMapLatest { query -> repository.getFilteredData(query) } fun setQuery(query: String) { savedStateHandle["query"] = query } }
Supporto dello stato della scrittura sperimentale
L'elemento lifecycle-viewmodel-compose
fornisce le API sperimentali
saveable
che consentono l'interoperabilità tra SavedStateHandle
e Saver
di Compose, in modo che qualsiasi State
che tu possa
salvare tramite rememberSaveable
con un Saver
personalizzato possa essere salvato anche con SavedStateHandle
.
Kotlin
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 conservati all'interno di un elemento SavedStateHandle
vengono salvati e ripristinati come
Bundle
, insieme al resto dell'elemento
savedInstanceState
dell'attività o del frammento.
Tipi supportati direttamente
Per impostazione predefinita, puoi chiamare set()
e get()
su SavedStateHandle
per gli stessi tipi di dati di Bundle
, come mostrato di seguito:
Supporto tipo/corsi | Supporto 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 comprimibile aggiungendo l'annotazione Kotlin @Parcelize
o implementando direttamente Parcelable
.
Salvataggio delle classi non comparabili
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 Bundle
usando il metodo setSavedStateProvider()
. SavedStateRegistry.SavedStateProvider
è un'interfaccia che definisce un singolo metodo
saveState()
che restituisce Bundle
contenente lo stato che vuoi salvare. Quando SavedStateHandle
è pronto per salvare il proprio stato, chiama saveState()
per recuperare Bundle
da SavedStateProvider
e salva Bundle
per la chiave associata.
Prendiamo come esempio un'app che richiede un'immagine all'app Fotocamera tramite l'intent ACTION_IMAGE_CAPTURE
, trasmettendo un file temporaneo in cui la fotocamera deve archiviare l'immagine. TempFileViewModel
incapsula la logica per la creazione del file temporaneo.
Kotlin
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel() { } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } }
Per garantire che il file temporaneo non vada perso se il processo dell'attività viene interrotto e poi ripristinato, TempFileViewModel
può utilizzare SavedStateHandle
per rendere persistenti i dati. Per consentire a TempFileViewModel
di salvare i suoi dati, implementa SavedStateProvider
e impostalo come provider nel SavedStateHandle
del ViewModel
:
Kotlin
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 } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel(SavedStateHandle savedStateHandle) { savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider()); } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider { @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); if (tempFile != null) { bundle.putString("path", tempFile.getAbsolutePath()); } return bundle; } } }
Per ripristinare i dati File
quando l'utente torna, recupera temp_file
Bundle
da SavedStateHandle
. Si tratta dello stesso valore Bundle
fornito da saveTempFile()
che contiene il percorso assoluto. È quindi possibile utilizzare il percorso assoluto
per creare un'istanza di un nuovo File
.
Kotlin
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 } } }
Java
class TempFileViewModel extends ViewModel { private File tempFile = null; public TempFileViewModel(SavedStateHandle savedStateHandle) { Bundle tempFileBundle = savedStateHandle.get("temp_file"); if (tempFileBundle != null) { tempFile = TempFileSavedStateProvider.restoreTempFile(tempFileBundle); } savedStateHandle.setSavedStateProvider("temp_file", new TempFileSavedStateProvider()); } @NonNull public File createOrGetTempFile() { if (tempFile == null) { tempFile = File.createTempFile("temp", null); } return tempFile; } private class TempFileSavedStateProvider implements SavedStateRegistry.SavedStateProvider { @NonNull @Override public Bundle saveState() { Bundle bundle = new Bundle(); if (tempFile != null) { bundle.putString("path", tempFile.getAbsolutePath()); } return bundle; } @Nullable private static File restoreTempFile(Bundle bundle) { if (bundle.containsKey("path") { return File(bundle.getString("path")); } return null; } } }
SalvatoStateHandle nei test
Per testare un ViewModel
che prende un SavedStateHandle
come dipendenza, crea una nuova istanza di SavedStateHandle
con i valori di test richiesti e passala all'istanza ViewModel
che stai testando.
Kotlin
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 Stato salvato per ViewModel
, consulta le risorse seguenti.
Codelab
Consigliato per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Salvare gli stati dell'interfaccia utente
- Utilizzare gli oggetti di dati osservabili
- Creare ViewModel con dipendenze