SavedState-Modul für ViewModel Teil von Android Jetpack.
Wie unter UI-Zustände speichern erwähnt, können ViewModel-Objekte Konfigurationsänderungen verarbeiten. Sie müssen sich also keine Gedanken über den Zustand bei Drehungen oder anderen Fällen machen. Wenn du jedoch eine vom System initiierte Prozessbeendigung verarbeiten musst, kannst du die SavedStateHandle API als Sicherung verwenden.
Der UI-Status wird normalerweise in ViewModel-Objekten gespeichert oder referenziert. Daher ist für die Verwendung von rememberSaveable in Compose ein gewisser Boilerplate-Code erforderlich, den das Modul für gespeicherte Status für Sie übernehmen kann.
Wenn Sie dieses Modul verwenden, erhalten ViewModel-Objekte über ihren Konstruktor ein SavedStateHandle-Objekt. Dieses Objekt ist eine Schlüssel/Wert-Zuordnung, mit der Sie Objekte in den gespeicherten Status schreiben und daraus abrufen können. Diese Werte bleiben erhalten, nachdem der Prozess vom System beendet wurde, und sind über dasselbe Objekt weiterhin verfügbar.
Der gespeicherte Status ist an Ihren Aufgabenstapel gebunden. Wenn der Aufgabenstapel verschwindet, geht auch der gespeicherte Status verloren. Das kann passieren, wenn eine App beendet, 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 Zustand nicht wiederherstellen. In Szenarien, in denen der UI-Status vom Nutzer geschlossen wird, wird der gespeicherte Status nicht wiederhergestellt. In vom System initiierten Szenarien ist dies der Fall.
Einrichtung
Wenn Sie SavedStateHandle verwenden möchten, akzeptieren Sie es als Konstruktorargument für ViewModel.
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Anschließend können Sie eine Instanz von ViewModel in Ihren Composables abrufen, ohne dass eine zusätzliche Konfiguration erforderlich ist. Die Standard-ViewModel-Factory stellt die entsprechende SavedStateHandle für Ihre ViewModel bereit.
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
Wenn Sie eine benutzerdefinierte ViewModelProvider.Factory-Instanz bereitstellen, können Sie die Verwendung von SavedStateHandle mit CreationExtras und der viewModelFactory-DSL aktivieren.
Mit SavedStateHandle arbeiten
Die Klasse SavedStateHandle ist eine Schlüssel/Wert-Zuordnung, mit der Sie Daten über die Methoden set() und get() in den gespeicherten Status schreiben und daraus abrufen können.
Wenn Sie SavedStateHandle verwenden, bleibt der Abfragewert auch bei einer Prozessbeendigung erhalten. So wird sichergestellt, dass der Nutzer vor und nach der Neuerstellung dieselben gefilterten Daten sieht, ohne dass die Aktivität oder das Fragment diesen Wert manuell speichern, wiederherstellen und an das ViewModel zurückleiten muss.
SavedStateHandle bietet auch andere Methoden, die Sie bei der Interaktion mit einer Schlüssel/Wert-Zuordnung erwarten können:
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 inSavedStateHandleenthalten sind.
Außerdem können Sie Werte aus SavedStateHandle mithilfe eines beobachtbaren Datenhalters abrufen. Die Liste der unterstützten Typen umfasst Folgendes:
StateFlow
Sie können Werte aus SavedStateHandle abrufen, die in einem StateFlow-Observable verpackt sind. Je nachdem, ob Sie den Wert direkt ändern müssen, können Sie zwischen einem Lesezugriff oder änderbaren Stream wählen:
getStateFlow(): Verwenden Sie diese Option, wenn Sie nur den Status lesen müssen. Wenn Sie den Wert des Schlüssels an anderer Stelle imSavedStateHandleaktualisieren, erhält der StateFlow den neuen Wert. Das ist ideal, wenn Sie einen schreibgeschützten Stream bereitstellen und ihn mit Flow-Operatoren transformieren möchten.getMutableStateFlow(): Verwenden Sie diese Option, wenn Sie sowohl Lese- als auch Schreibzugriff benötigen. Wenn Sie den.valuedes zurückgegebenenMutableStateFlowaktualisieren, wird der zugrunde liegendeSavedStateHandleautomatisch aktualisiert. Sie müssen den Schlüssel also nicht manuell festlegen.
Meistens aktualisieren Sie diese Werte aufgrund von Nutzerinteraktionen, z. B. wenn eine Abfrage zum Filtern einer Datenliste eingegeben wird.
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 } }
Unterstützung für KotlinX-Serialisierung
Für komplexe UI-Zustände können Sie den Property-Delegaten saved zusammen mit der KotlinX-Serialisierung verwenden. Mit diesem Delegat kannst du benutzerdefinierte @Serializable-Datenklassen direkt in der SavedStateHandle speichern. So bleibt der Status des ViewModels auch bei einer Prozessbeendigung erhalten und die Compose-UI kann ihren Status bei der Neuerstellung nahtlos wiederherstellen.
Dazu annotieren Sie Ihre Datenklasse mit @Serializable und verwenden den saved-Delegaten in Ihrem 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) } }
Unterstützung für Compose-Zustand
Wenn Ihr Status auf den Saver-APIs von Compose statt auf KotlinX Serialization basiert, stellt das Artefakt lifecycle-viewmodel-compose den Delegaten saveable bereit. Dies ermöglicht die Interoperabilität zwischen SavedStateHandle und Saver von Compose, sodass alle State, die Sie über rememberSaveable mit einem benutzerdefinierten Saver speichern können, auch mit SavedStateHandle gespeichert werden können.
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 den restlichen savedInstanceState für deine App 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 für 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, sollten Sie die Klasse parcelable machen, indem Sie die Kotlin-Annotation @Parcelize hinzufügen oder Parcelable direkt implementieren.
Nicht parcelable-fähige Klassen speichern
Wenn eine Klasse Parcelable oder Serializable nicht implementiert und nicht so geändert werden kann, dass sie eine dieser Schnittstellen implementiert, ist es nicht möglich, eine Instanz dieser Klasse direkt in einem SavedStateHandle zu speichern.
Ab Lifecycle 2.3.0-alpha03 können Sie mit SavedStateHandle jedes Objekt speichern, indem Sie Ihre eigene Logik zum Speichern und Wiederherstellen des Objekts als Bundle mit der Methode setSavedStateProvider() bereitstellen.
SavedStateRegistry.SavedStateProvider ist eine Schnittstelle, die eine einzelne saveState()-Methode definiert, die ein Bundle mit dem Status zurückgibt, den Sie speichern möchten. Wenn SavedStateHandle bereit ist, den Status zu speichern, wird saveState() aufgerufen, um die Bundle aus der SavedStateProvider abzurufen, und die Bundle für den zugehörigen Schlüssel gespeichert.
Angenommen, eine App fordert über den Intent ACTION_IMAGE_CAPTURE ein Bild von der Kamera-App an und übergibt eine temporäre Datei, in der die Kamera das Bild speichern soll. Die TempFileViewModel kapselt die Logik zum Erstellen dieser temporären Datei.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
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 beizubehalten. Damit TempFileViewModel seine Daten speichern kann, implementiere SavedStateProvider und lege es als Anbieter für SavedStateHandle von ViewModel fest:
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 } } }
Wenn der Nutzer zurückkehrt, können Sie die File-Daten wiederherstellen, indem Sie die temp_file
Bundle aus dem SavedStateHandle abrufen. Dies ist derselbe Bundle, der von saveTempFile() bereitgestellt wird und den absoluten Pfad enthält. Der absolute Pfad kann dann verwendet werden, um ein neues File zu instanziieren.
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 in Tests
Wenn Sie einen ViewModel testen möchten, der einen 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.
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Zusätzliche Ressourcen
Weitere Informationen zum Modul „Gespeicherter Status“ für ViewModel finden Sie in den folgenden Ressourcen.
Codelabs
Inhalte ansehen
Empfehlungen für Sie
- Hinweis: Der Linktext wird angezeigt, wenn JavaScript deaktiviert ist.
- UI-Zustände speichern
- Mit beobachtbaren Datenobjekten arbeiten
- ViewModels mit Abhängigkeiten erstellen