Modul „Saved State“ für ViewModel Teil von Android Jetpack

Wie bereits erwähnt, Benutzeroberflächenstatus speichern, ViewModel-Objekte können Konfigurationsänderungen. Sie müssen sich also keine Gedanken über den Status in Rotationen machen. oder anderen Fällen. Wenn Sie jedoch einen vom System initiierten Prozess Tod ist, können Sie die SavedStateHandle API als Sicherung verwenden.

Der UI-Status wird normalerweise in ViewModel-Objekten gespeichert oder referenziert, nicht Aktivitäten. Daher sind für die Verwendung von onSaveInstanceState() oder rememberSaveable Textbaustein, der das Modul für gespeicherte Status das für Sie übernehmen kann.

Bei Verwendung dieses Moduls erhalten ViewModel-Objekte ein Objekt SavedStateHandle über seinen Konstruktor abrufen. Dieses Objekt ist eine Schlüssel/Wert-Zuordnung, mit der Sie Objekte in den und aus dem gespeicherten Status schreiben bzw. daraus abrufen. Diese Werte bleiben bestehen, nachdem der Prozess vom System beendet wurde, und weiterhin verfügbar sind. über dasselbe Objekt.

Der gespeicherte Status ist an Ihren Aufgabenstack gebunden. Wenn Ihr Aufgabenstapel verschwindet, werden Ihre gespeicherten verschwindet. Dies kann passieren, wenn das Beenden einer App erzwungen, aus dem Menü „Zuletzt verwendet“ aus oder starte das Gerät neu. In solchen Fällen muss die Aufgabe verschwindet und Sie können die Informationen nicht im gespeicherten Zustand wiederherstellen. In Vom Nutzer initiierte Ablehnung des UI-Status wird der gespeicherte Status nicht wiederhergestellt. In vom System initiiert und Szenarien.

Einrichten

Ab Fragment 1.2.0 oder seine transitive Abhängigkeit Aktivität 1.1.0, können Sie SavedStateHandle als Konstruktorargument für Ihre ViewModel festlegen.

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. Die standardmäßige ViewModel-Factory stellt den entsprechenden SavedStateHandle auf Ihr ViewModel.

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 können Sie die Nutzung von SavedStateHandle aktivieren, indem Sie AbstractSavedStateViewModelFactory

Mit SavedStateHandle arbeiten

Die SavedStateHandle-Klasse ist eine Schlüssel/Wert-Zuordnung, mit der Sie schreiben und Daten aus dem gespeicherten Zustand über die set() und get() .

Durch die Verwendung von SavedStateHandle wird der Abfragewert über den Prozessbeendigung hinaus beibehalten. Der Nutzer sieht vor und nach der Änderung denselben Satz gefilterter Daten. -Neuerstellung, ohne dass die Aktivität oder das Fragment manuell gespeichert, wiederhergestellt und leiten diesen Wert zurück an ViewModel.

SavedStateHandle hat auch andere Methoden, die du bei der Interaktion erwarten kannst Schlüssel/Wert-Paar-Zuordnung:

  • contains(String key) Prü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() – Rückgaben alle Schlüssel, die in SavedStateHandle enthalten sind.

Außerdem können Sie Werte aus SavedStateHandle abrufen, indem Sie einen für beobachtbare Daten. Folgende Typen werden unterstützt:

LiveData

Werte aus SavedStateHandle abrufen, die in ein LiveData beobachtbar mit getLiveData() Wenn der Wert des Schlüssels aktualisiert wird, erhält LiveData den neuen Wert. Meiste wird der Wert häufig aufgrund von Nutzerinteraktionen festgelegt, z. B. der Eingabe einer Anfrage an um eine Datenliste zu filtern. Mit diesem aktualisierten Wert können Sie dann LiveData 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);
    }
}

Zustandsfluss

Werte aus SavedStateHandle abrufen, die in ein StateFlow beobachtbar mit getStateFlow() Wenn Sie den Wert des Schlüssels aktualisieren, erhält StateFlow den neuen Wert. Meiste Häufig wird der Wert aufgrund von Nutzerinteraktionen festgelegt, z. B. der Eingabe eines zum Filtern einer Datenliste. Sie können diesen aktualisierten Wert dann mit anderen Flow-Operatoren.

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 für experimentelle Funktionen von „Compose“

Das Artefakt lifecycle-viewmodel-compose stellt das experimentelle saveable APIs für die Interoperabilität zwischen SavedStateHandle und Compose Saver, sodass alle State kann speichern über rememberSaveable mit einem benutzerdefinierten Saver 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 in einem SavedStateHandle werden gespeichert und wiederhergestellt als Bundle, zusammen mit dem Rest der savedInstanceState für die Aktivität oder das Fragment.

Direkt unterstützte Typen

Standardmäßig können Sie set() und get() auf einem SavedStateHandle für die dieselben Datentypen wie Bundle, wie unten gezeigt:

Support für Typen/Klassen Arrayunterstü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 eine der in der obigen Liste genannten nicht erweitert, sollten Sie die Klasse parcelable durch Hinzufügen der Klasse @Parcelize Kotlin-Anmerkungen oder -Implementierungen Parcelable direkt.

Nicht-parzellierbare Klassen speichern

Wenn eine Klasse Parcelable oder Serializable nicht implementiert und nicht geändert wurden, um eine dieser Schnittstellen zu implementieren, eine Instanz dieser Klasse direkt in einer SavedStateHandle speichern.

Beginnend mit Lebenszyklus 2.3.0-alpha03 Mit SavedStateHandle können Sie jedes Objekt speichern, indem Sie Ihr eigenes zum Speichern und Wiederherstellen des Objekts als Bundle mithilfe des setSavedStateProvider() . SavedStateRegistry.SavedStateProvider ist eine Schnittstelle, die ein einzelnes saveState() , die einen Bundle mit dem zu speichernden Status zurückgibt. Wann? SavedStateHandle kann den Status speichern und ruft saveState() auf um die Bundle aus SavedStateProvider abzurufen und die Bundle für den zugehörigen Schlüssel.

Hier ein Beispiel für eine App, die ein Bild von der Kamera-App über ACTION_IMAGE_CAPTURE Intent erstellt und eine temporäre Datei für den Speicherort der Kamera Bild. TempFileViewModel enthält die Logik zum Erstellen dieses temporäre 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;
    }
}

Um sicherzustellen, dass die temporäre Datei nicht verloren geht, wenn der Prozess der Aktivität beendet wird und später wiederhergestellt, kann TempFileViewModel mit SavedStateHandle Folgendes tun: ihre Daten beibehalten. Damit TempFileViewModel Daten speichern kann, implementieren Sie SavedStateProvider und legen Sie es als Anbieter im SavedStateHandle von ViewModel:

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 den temp_file ab. Bundle vom SavedStateHandle. Dies ist die gleiche Bundle, die von saveTempFile(), der den absoluten Pfad enthält. Der absolute Pfad kann dann wird verwendet, 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

Zum Testen einer ViewModel, die eine SavedStateHandle als Abhängigkeit annimmt, erstellen Sie eine neue Instanz von SavedStateHandle mit den erforderlichen Testwerten und übergeben in die ViewModel-Instanz, die Sie testen.

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 „Saved State“ für ViewModel finden Sie in der in den folgenden Ressourcen.

Codelabs