Modul „Saved State“ für ViewModel Teil von Android Jetpack.
Wie unter UI-Status speichern erwähnt, können ViewModel
-Objekte Konfigurationsänderungen verarbeiten. Sie müssen sich also keine Gedanken über den Status in Rotationen oder anderen Fällen machen. Wenn Sie jedoch mit dem Tod eines vom System initiierten Prozesses umgehen müssen, können Sie die SavedStateHandle
API als Sicherung verwenden.
Der UI-Status wird normalerweise in ViewModel
-Objekten und nicht in Aktivitäten gespeichert oder referenziert. Daher ist für die Verwendung von onSaveInstanceState()
oder rememberSaveable
ein Textbaustein erforderlich, den das gespeicherte Statusmodul für Sie verarbeiten kann.
Wenn Sie dieses Modul verwenden, empfangen ViewModel
-Objekte ein SavedStateHandle
-Objekt über ihren Konstruktor. Dieses Objekt ist eine Schlüssel/Wert-Zuordnung, mit der Sie Objekte in den und aus dem gespeicherten Status schreiben und abrufen können. Diese Werte bleiben bestehen, nachdem der Prozess vom System beendet wurde, und bleiben über dasselbe Objekt verfügbar.
Der gespeicherte Status ist an Ihren Aufgaben-Stack gebunden. Wenn der Aufgaben-Stack verschwindet, verschwindet auch der gespeicherte Status. Dies kann passieren, wenn das Beenden einer App erzwungen, die App aus dem Menü „Zuletzt verwendet“ entfernt oder das Gerät neu gestartet wird. In solchen Fällen verschwindet der Aufgabenstapel und Sie können die Informationen im gespeicherten Status nicht wiederherstellen. In Szenarien unter Vom Nutzer initiierte Ablehnung des UI-Status wird der gespeicherte Status nicht wiederhergestellt. In vom System initiierten Szenarien ist dies der Fall.
Einrichten
Ab Fragment 1.2.0 oder der transitiven Abhängigkeit Activity 1.1.0 können Sie ein SavedStateHandle
als Konstruktorargument für Ihre ViewModel
akzeptieren.
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 standardmäßige ViewModel
-Factory stellt den entsprechenden SavedStateHandle
für 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 bereitstellen, können Sie die Nutzung von SavedStateHandle
aktivieren, indem Sie AbstractSavedStateViewModelFactory
erweitern.
Mit SavedStateHandle arbeiten
Die Klasse SavedStateHandle
ist eine Schlüssel/Wert-Zuordnung, mit der Sie mithilfe der Methoden set()
und get()
Daten in den und aus dem gespeicherten Status schreiben und abrufen können.
Durch die Verwendung von SavedStateHandle
wird der Abfragewert über den Prozessabschluss hinweg beibehalten, damit der Nutzer vor und nach der Neuerstellung denselben Satz gefilterter Daten sieht, ohne dass die Aktivität oder das Fragment manuell speichern, wiederherstellen und an den ViewModel
weiterleiten muss.
Für SavedStateHandle
gibt es auch andere Methoden, die Sie bei der Interaktion mit einer Schlüssel/Wert-Zuordnung erwarten können:
contains(String key)
: Überprüft, ob für den angegebenen Schlüssel ein Wert 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 mit einem beobachtbaren Datenbehälter Werte aus SavedStateHandle
abrufen. Folgende Typen werden unterstützt:
LiveData
Rufen Sie mit getLiveData()
Werte aus SavedStateHandle
ab, die in ein beobachtbares LiveData
eingeschlossen 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. die Eingabe einer Abfrage zum Filtern einer Liste von Daten. Dieser aktualisierte Wert kann dann zum Transformieren von LiveData
verwendet werden.
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); } }
Statusfluss
Rufen Sie mit getStateFlow()
Werte aus SavedStateHandle
ab, die in ein beobachtbares StateFlow
eingeschlossen sind.
Wenn Sie den Wert des Schlüssels aktualisieren, erhält StateFlow
den neuen Wert. In den meisten Fällen können Sie den Wert aufgrund von Nutzerinteraktionen festlegen, z. B. die Eingabe einer Abfrage zum Filtern einer Liste von Daten. Anschließend können Sie diesen aktualisierten Wert 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 „Experimental Compose“
Das lifecycle-viewmodel-compose
-Artefakt stellt die experimentellen saveable
APIs bereit, die die Interoperabilität zwischen SavedStateHandle
und der Saver
von Compose ermöglichen. So können alle State
, die Sie über rememberSaveable
mit einem 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
enthalten sind, werden zusammen mit den restlichen savedInstanceState
-Elementen 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 ein SavedStateHandle
-Objekt mit den gleichen Datentypen wie ein Bundle
aufrufen, wie unten dargestellt:
Support für Typen/Kurse | 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 keinen der oben genannten Punkte erweitert, sollten Sie die Klasse parcelable machen. Fügen Sie dazu die Kotlin-Annotation @Parcelize
hinzu oder implementieren Sie Parcelable
direkt.
Klassen ohne Parzellen speichern
Wenn eine Klasse Parcelable
oder Serializable
nicht implementiert und nicht geändert werden kann, um eine dieser Schnittstellen zu implementieren, ist es nicht möglich, eine Instanz dieser Klasse direkt in einer SavedStateHandle
zu speichern.
Ab Lebenszyklus 2.3.0-alpha03 können Sie mit SavedStateHandle
jedes Objekt speichern. Dazu stellen Sie Ihre eigene Logik zum Speichern und Wiederherstellen des Objekts als Bundle
mithilfe der Methode setSavedStateProvider()
bereit. SavedStateRegistry.SavedStateProvider
ist eine Schnittstelle, die eine einzelne saveState()
-Methode definiert, die einen Bundle
-Wert zurückgibt, der den zu speichernden Status enthält. Wenn SavedStateHandle
zum Speichern des Status bereit ist, wird saveState()
aufgerufen, um die Bundle
aus SavedStateProvider
abzurufen, und speichert die Bundle
für den zugehörigen Schlüssel.
Hier sehen Sie ein Beispiel für eine App, die über den Intent ACTION_IMAGE_CAPTURE
ein Bild von der Kamera-App anfordert und dabei eine temporäre Datei übergibt, in der die Kamera das Bild speichern soll. TempFileViewModel
enthält die Logik zum Erstellen dieser temporären Datei.
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, falls der Prozess der Aktivität beendet und später wiederhergestellt wird, kann TempFileViewModel
die SavedStateHandle
verwenden, um die Daten beizubehalten. Damit TempFileViewModel
seine Daten speichern kann, musst du SavedStateProvider
implementieren und im SavedStateHandle
der ViewModel
als Anbieter festlegen:
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 du die File
-Daten wiederherstellen möchtest, wenn der Nutzer zurückkehrt, rufe die temp_file
-Bundle
aus SavedStateHandle
ab. Dies ist dieselbe Bundle
, die von saveTempFile()
bereitgestellt wird und den absoluten Pfad enthält. Der absolute Pfad kann dann verwendet werden, um eine neue File
zu instanziieren.
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 ein ViewModel
testen möchten, das SavedStateHandle
als Abhängigkeit verwendet, 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