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