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 in SavedStateHandle 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