Modulo Saved State per ViewModel Componente di Android Jetpack.
Come accennato in Salvare gli 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 l'interruzione del processo avviata dal sistema, ti consigliamo di utilizzare l'API SavedStateHandle
come backup.
Lo stato dell'interfaccia utente viene in genere archiviato o a cui viene fatto riferimento negli oggetti ViewModel
e non nelle attività, pertanto l'utilizzo di onSaveInstanceState()
o rememberSaveable
richiede del codice boilerplate che il modulo dello stato salvato può gestire per te.
Quando utilizzi questo modulo, gli oggetti ViewModel
ricevono un oggetto
SavedStateHandle
tramite il relativo costruttore. Questo oggetto è una mappa chiave-valore che ti consente di scrivere e recuperare oggetti verso e dallo stato salvato. Questi valori rimangono invariati dopo l'interruzione del processo da parte del sistema e rimangono disponibili tramite lo stesso oggetto.
Lo stato salvato è legato allo stack di attività. Se la serie di attività scompare, scompare anche lo stato salvato. Ciò può verificarsi quando interrompi forzatamente un'app, rimuovi l'app dal menu App recenti o riavvii il dispositivo. In questi casi, la serie di compiti desaparece 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. Nei scenari iniziati dal sistema, è così.
Configura
A partire da Fragment 1.2.0 o dalla sua dipendenza transitiva Activity 1.1.0, puoi accettare un SavedStateHandle
come argomento del costruttore per 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. La ViewModel
di fabbrica predefinita fornisce il 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 personalizzata di ViewModelProvider.Factory
, puoi attivare l'utilizzo di SavedStateHandle
estendendo AbstractSavedStateViewModelFactory
.
Utilizzo di SavedStateHandle
La classe SavedStateHandle
è una mappa chiave-valore che consente di scrivere e recuperare i dati da e verso lo stato salvato tramite i metodi set()
e get()
.
Se utilizzi SavedStateHandle
, il valore della query viene mantenuto dopo l'interruzione del processo,
in modo che l'utente visualizzi lo stesso insieme di dati filtrati prima e dopo
la ricreazione senza che l'attività o il frammento debba salvare, ripristinare manualmente
e inoltrare nuovamente il valore a ViewModel
.
SavedStateHandle
dispone anche di 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 detentore di dati osservabile. L'elenco dei tipi supportati è il seguente:
LiveData
Recupera i valori da SavedStateHandle
racchiusi in un osservatore
LiveData
utilizzando
getLiveData()
.
Quando il valore della chiave viene aggiornato, LiveData
riceve il nuovo valore. Molto spesso, 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); } }
StateFlow
Recupera i valori da SavedStateHandle
racchiusi in un osservatore
StateFlow
utilizzando
getStateFlow()
.
Quando aggiorni il valore della chiave, StateFlow
riceve il nuovo valore. Molto spesso, puoi impostare il valore in base alle interazioni degli utenti, ad esempio inserendo una query per filtrare un elenco di dati. Poi puoi 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 di Compose 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 puoi
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 in un SavedStateHandle
vengono salvati e ripristinati come
Bundle
, insieme al resto del
savedInstanceState
per l'attività o il frammento.
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 di tipo/classe | Supporto degli 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 parcellabile aggiungendo l'annotazione Kotlin @Parcelize
o implementando direttamente Parcelable
.
Salvataggio di classi non frazionabili
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 il salvataggio e il ripristino dell'oggetto come
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 a salvare il proprio stato, chiama saveState()
per recuperare il Bundle
dal SavedStateProvider
e salva il
Bundle
per la chiave associata.
Prendiamo ad esempio un'app che richiede un'immagine dall'app Fotocamera tramite
l'intent ACTION_IMAGE_CAPTURE
, passando un file temporaneo per la posizione in cui la fotocamera deve memorizzare
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 assicurarsi che il file temporaneo non venga perso se il processo dell'attività viene interrotto
e successivamente ripristinato, TempFileViewModel
può utilizzare SavedStateHandle
per memorizzare 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 di File
quando l'utente torna, recupera temp_file
Bundle
da SavedStateHandle
. Si tratta dello stesso Bundle
fornito da
saveTempFile()
che contiene il percorso assoluto. Il percorso assoluto può quindi essere utilizzato per creare 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; } } }
SavedStateHandle nei test
Per testare un ViewModel
che ha 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 seguenti risorse.
Codelab
Consigliati per te
- Nota: il testo del link viene visualizzato quando JavaScript è disattivato
- Salvare gli stati dell'interfaccia utente
- Lavorare con oggetti dati osservabili
- Creare ViewModel con dipendenze