Modul für den gespeicherten Status für ViewModel Teil von Android Jetpack.
Wie bereits im Abschnitt UI-Status speichern erwähnt, können ViewModel
-Objekte Konfigurationsänderungen verarbeiten. Sie müssen sich also keine Gedanken über den Status bei Drehungen oder anderen Fällen machen. Wenn Sie jedoch einen systeminitiierten Prozessabbruch verarbeiten müssen, können Sie die SavedStateHandle
API als Back-up verwenden.
Der UI-Status wird normalerweise in ViewModel
-Objekten gespeichert oder darauf verwiesen, nicht in Aktivitäten. Daher ist für die Verwendung von onSaveInstanceState()
oder rememberSaveable
etwas Boilerplate-Code erforderlich, den das Modul für den gespeicherten Status für Sie verarbeiten kann.
Bei Verwendung dieses Moduls erhalten ViewModel
-Objekte über den Konstruktor ein SavedStateHandle
-Objekt. Dieses Objekt ist eine Schlüssel/Wert-Zuordnung, mit der Sie Objekte in den gespeicherten Zustand schreiben und daraus abrufen können. Diese Werte bleiben auch nach dem Beenden des Prozesses durch das System erhalten und sind über dasselbe Objekt verfügbar.
Der gespeicherte Status ist mit Ihrem Aufgabenstapel verknüpft. Wenn der Aufgabenstapel verschwindet, wird auch der gespeicherte Status gelöscht. Das kann passieren, wenn Sie eine App erzwungen beenden, sie aus dem Menü „Zuletzt geöffnet“ entfernen oder das Gerät neu starten. In solchen Fällen verschwindet der Aufgabenstapel und die Informationen können nicht im gespeicherten Zustand wiederhergestellt werden. In Szenarien mit vom Nutzer initiierter Schließung des UI-Status wird der gespeicherte Status nicht wiederhergestellt. Bei vom System initiierten Szenarien ist das der Fall.
Einrichten
Ab Fragment 1.2.0 oder seiner übergeordneten Abhängigkeit Aktivität 1.1.0 können Sie eine SavedStateHandle
als Konstruktorargument für Ihre ViewModel
annehmen.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Sie können dann eine Instanz Ihrer ViewModel
ohne zusätzliche Konfiguration abrufen. Die Standard-ViewModel
-Factory stellt die entsprechende SavedStateHandle
für Ihre ViewModel
bereit.
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); ... } ... }
Wenn Sie eine benutzerdefinierte ViewModelProvider.Factory
-Instanz angeben, können Sie die Verwendung von SavedStateHandle
aktivieren, indem Sie AbstractSavedStateViewModelFactory
erweitern.
Mit SavedStateHandle arbeiten
Die SavedStateHandle
-Klasse ist eine Schlüssel/Wert-Map, mit der Sie Daten über die Methoden set()
und get()
in den gespeicherten Zustand schreiben und daraus abrufen können.
Wenn du SavedStateHandle
verwendest, wird der Abfragewert bei Prozessende beibehalten. So sieht der Nutzer vor und nach der Neuerstellung dieselben gefilterten Daten, ohne dass die Aktivität oder das Fragment manuell gespeichert, wiederhergestellt und der Wert an ViewModel
weitergeleitet werden muss.
SavedStateHandle
bietet auch andere Methoden, die Sie bei der Interaktion mit einer Schlüssel/Wert-Zuordnung erwarten würden:
contains(String key)
: Prüft, ob ein Wert für den angegebenen Schlüssel vorhanden ist.remove(String key)
: Entfernt den Wert für den angegebenen Schlüssel.keys()
: Gibt alle Schlüssel zurück, die inSavedStateHandle
enthalten sind.
Außerdem können Sie Werte aus SavedStateHandle
mithilfe eines beobachtbaren Dateninhabers abrufen. Folgende Typen werden unterstützt:
LiveData
Werte aus SavedStateHandle
abrufen, die in einem LiveData
-Observablen mit getLiveData()
verpackt sind
Wenn der Wert des Schlüssels aktualisiert wird, erhält LiveData
den neuen Wert. In den meisten Fällen wird der Wert durch Nutzerinteraktionen festgelegt, z. B. durch Eingabe einer Abfrage zum Filtern einer Datenliste. Dieser aktualisierte Wert kann dann verwendet werden, um LiveData
zu transformieren.
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
Werte aus SavedStateHandle
abrufen, die in einem StateFlow
-Observablen mit getStateFlow()
verpackt sind
Wenn Sie den Wert des Schlüssels aktualisieren, erhält die StateFlow
den neuen Wert. In den meisten Fällen legen Sie den Wert aufgrund von Nutzerinteraktionen fest, z. B. durch Eingabe einer Abfrage, um eine Datenliste zu filtern. Sie können diesen aktualisierten Wert dann mit anderen Ablaufoperatoren transformieren.
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 } }
Unterstützung des Status der experimentellen Funktion „Compose“
Das lifecycle-viewmodel-compose
-Artefakt enthält die experimentellen saveable
-APIs, die die Interoperabilität zwischen SavedStateHandle
und der Saver
von Compose ermöglichen. So können alle State
, die Sie über rememberSaveable
mit einer benutzerdefinierten Saver
speichern können, auch mit SavedStateHandle
gespeichert werden.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Unterstützte Typen
Daten, die in einem SavedStateHandle
gespeichert sind, werden zusammen mit dem Rest des savedInstanceState
für die Aktivität oder das Fragment als Bundle
gespeichert und wiederhergestellt.
Direkt unterstützte Typen
Standardmäßig können Sie set()
und get()
für eine SavedStateHandle
für dieselben Datentypen wie eine Bundle
aufrufen, wie unten gezeigt:
Unterstützung für Typ/Klasse | Array-Unterstützung |
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+) |
Wenn die Klasse keine der Klassen in der obigen Liste erweitert, können Sie sie paketierbar machen, indem Sie die Kotlin-Anmerkung @Parcelize
hinzufügen oder Parcelable
direkt implementieren.
Nicht parzellierbare Klassen speichern
Wenn eine Klasse weder Parcelable
noch Serializable
implementiert und nicht so geändert werden kann, dass eine dieser Schnittstellen implementiert wird, ist es nicht möglich, eine Instanz dieser Klasse direkt in einer SavedStateHandle
zu speichern.
Ab Lifecycle 2.3.0-alpha03 können Sie mit SavedStateHandle
jedes Objekt speichern, indem Sie mit der Methode setSavedStateProvider()
eine eigene Logik zum Speichern und Wiederherstellen des Objekts als Bundle
angeben. SavedStateRegistry.SavedStateProvider
ist eine Schnittstelle, die eine einzelne saveState()
-Methode definiert, die eine Bundle
zurückgibt, die den zu speichernden Status enthält. Wenn SavedStateHandle
bereit ist, seinen Status zu speichern, ruft er saveState()
auf, um die Bundle
aus der SavedStateProvider
abzurufen, und speichert die Bundle
für den zugehörigen Schlüssel.
Angenommen, eine App fordert über den Intent ACTION_IMAGE_CAPTURE
ein Bild von der Kamera an und gibt eine temporäre Datei an, in der die Kamera das Bild speichern soll. Die TempFileViewModel
kapselt die Logik zum Erstellen dieser temporären Datei ein.
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; } }
Damit die temporäre Datei nicht verloren geht, wenn der Prozess der Aktivität beendet und später wiederhergestellt wird, kann TempFileViewModel
die SavedStateHandle
verwenden, um die Daten zu speichern. Damit TempFileViewModel
seine Daten speichern kann, implementieren Sie SavedStateProvider
und legen Sie es als Anbieter in der SavedStateHandle
der ViewModel
fest:
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; } } }
Wenn der Nutzer zurückkehrt, können Sie die File
-Daten wiederherstellen, indem Sie die temp_file
Bundle
aus dem SavedStateHandle
abrufen. Dies ist dieselbe Bundle
, die von saveTempFile()
bereitgestellt wird und den absoluten Pfad enthält. Mit dem absoluten Pfad kann dann eine neue File
-Instanz erstellt werden.
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 in Tests
Wenn Sie einen ViewModel
testen möchten, der eine SavedStateHandle
als Abhängigkeit hat, erstellen Sie eine neue Instanz von SavedStateHandle
mit den erforderlichen Testwerten und übergeben Sie sie an die zu testende ViewModel
-Instanz.
Kotlin
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Weitere Informationen
Weitere Informationen zum Modul „Gespeicherter Status“ für ViewModel
finden Sie in den folgenden Ressourcen.
Codelabs
Empfehlungen für dich
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- UI-Zustände speichern
- Mit beobachtbaren Datenobjekten arbeiten
- ViewModels mit Abhängigkeiten erstellen