Moduł Saved State dla ViewModel (Views) Część Androida Jetpack.
Koncepcje i implementacja w Jetpack Compose
Jak wspomnieliśmy w artykule Zapisywanie stanów interfejsu, obiekty ViewModel mogą obsługiwać
zmiany konfiguracji, więc nie musisz się martwić o stan w przypadku obrotu ekranu
ani w innych sytuacjach. Jeśli jednak musisz obsługiwać zakończenie procesu zainicjowane przez system, możesz użyć interfejsu SavedStateHandle API jako kopii zapasowej.
Stan interfejsu jest zwykle przechowywany w obiektach ViewModel lub do nich odwoływany, a nie
w aktywnościach, więc użycie onSaveInstanceState() wymaga pewnego kodu, który może obsłużyć
moduł saved state.
Gdy używasz tego modułu, ViewModel obiekty otrzymują obiekt SavedStateHandle
za pomocą konstruktora. Ten obiekt to mapa klucz-wartość, która umożliwia zapisywanie i pobieranie obiektów do i z zapisanego stanu. Te wartości są zachowywane po zakończeniu procesu przez system i pozostają dostępne za pomocą tego samego obiektu.
Saved state jest powiązany ze stosem zadań. Jeśli stos zadań zniknie, zniknie też saved state. Może się to zdarzyć, gdy wymusisz zatrzymanie aplikacji, usuniesz ją z menu ostatnich aplikacji lub ponownie uruchomisz urządzenie. W takich przypadkach stos zadań znika i nie można przywrócić informacji w saved state. W scenariuszach odrzucenia stanu interfejsu zainicjowanych przez użytkownika saved state nie jest przywracany. W scenariuszach zainicjowanych przez system jest.
W przypadku stanu używanego w logice biznesowej przechowuj go w ViewModel i zapisuj za pomocąSavedStateHandle. W przypadku stanu używanego w
logice interfejsu użyj interfejsu onSaveInstanceState API w systemie View.Konfiguracja
Począwszy od Fragment 1.2.0 lub jego zależności przechodniej
Activity 1.1.0, możesz zaakceptować SavedStateHandle jako argument konstruktora w ViewModel.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Następnie możesz pobrać instancję ViewModel bez dodatkowej konfiguracji. Domyślna fabryka ViewModel udostępnia odpowiedni SavedStateHandle do 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); ... } ... }
Podczas udostępniania niestandardowej instancji ViewModelProvider.Factory możesz
włączyć użycie SavedStateHandle, rozszerzając
AbstractSavedStateViewModelFactory.
lifecycle-viewmodel-savedstate i użyć
SavedStateViewModelFactory jako fabryki.Praca z SavedStateHandle
Klasa SavedStateHandle to mapa klucz-wartość, która umożliwia zapisywanie i
pobieranie danych do i z zapisanego stanu za pomocą metod set() i
get().
Dzięki użyciu SavedStateHandle wartość zapytania jest zachowywana po śmierci procesu, co zapewnia, że użytkownik widzi ten sam zestaw przefiltrowanych danych przed i po ponownym utworzeniu bez konieczności ręcznego zapisywania, przywracania i przekazywania tej wartości z powrotem do ViewModel przez aktywność lub fragment.
SavedStateHandle zapisuje tylko dane, które zostały do niego zapisane, gdy
Activity jest zatrzymana. Zapisywanie w SavedStateHandle podczas zatrzymania Activity nie jest zapisywane, chyba że Activity otrzyma onStart, a następnie ponownie onStop.SavedStateHandle ma też inne metody, których możesz się spodziewać podczas interakcji z mapą klucz-wartość:
contains(String key)– sprawdza, czy istnieje wartość dla danego klucza.remove(String key)– usuwa wartość dla danego klucza.keys()– zwraca wszystkie klucze zawarte wSavedStateHandle.
Dodatkowo możesz pobierać wartości z SavedStateHandle za pomocą obserwowalnego kontenera danych. Lista obsługiwanych typów:
get() i set(). SavedStateHandle jest zapisywany, gdy w połączonej aktywności lub fragmencie wywoływana jest funkcja
onSaveInstanceState.
Oznacza to, że chociaż możesz nadal aktualizować dane dostępne do obserwacji z SavedStateHandle, gdy aplikacja działa w tle, wszystkie aktualizacje stanu mogą zostać utracone, jeśli proces aplikacji zostanie zakończony, zanim aplikacja ponownie przejdzie na pierwszy plan.LiveData
Pobieraj wartości z SavedStateHandle, które są opakowane w LiveData
obserwowalny obiekt za pomocą getLiveData(). Gdy wartość klucza zostanie zaktualizowana, LiveData otrzyma nową wartość. Najczęściej wartość jest ustawiana w wyniku interakcji użytkownika, np. wpisania zapytania w celu przefiltrowania listy danych. Ta zaktualizowana
wartość może być następnie używana do przekształcania LiveData.
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); } }
Obsługiwane typy
Dane przechowywane w SavedStateHandle są zapisywane i przywracane jako Bundle,
wraz z resztą savedInstanceState dla aktywności lub
fragmentu.
Zapisywanie klas niepodlegających serializacji
Jeśli klasa nie implementuje Parcelable ani Serializable i nie można jej zmodyfikować tak, aby implementowała jeden z tych interfejsów, nie można bezpośrednio zapisać instancji tej klasy w SavedStateHandle.
Począwszy od Lifecycle 2.3.0-alpha03, SavedStateHandle umożliwia zapisywanie
dowolnego obiektu przez udostępnienie własnej logiki zapisywania i przywracania obiektu jako
Bundle za pomocą metody setSavedStateProvider().
SavedStateRegistry.SavedStateProvider to interfejs, który definiuje
pojedynczą metodę saveState(), która zwraca Bundle zawierający stan
do zapisania. Gdy SavedStateHandle jest gotowy do zapisania swojego stanu, wywołuje saveState(), aby pobrać Bundle z SavedStateProvider, i zapisuje Bundle dla powiązanego klucza.
Rozważmy przykład aplikacji, która wysyła żądanie obrazu z aplikacji aparatu za pomocą
intencji ACTION_IMAGE_CAPTURE, przekazując tymczasowy plik, w którym
aparat ma przechowywać obraz. TempFileViewModel zawiera logikę tworzenia tego pliku tymczasowego.
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; } }
Aby mieć pewność, że plik tymczasowy nie zostanie utracony, jeśli proces aktywności zostanie zakończony i później przywrócony, TempFileViewModel może użyć SavedStateHandle do utrwalenia swoich danych. Aby umożliwić TempFileViewModel zapisywanie danych, zaimplementuj
SavedStateProvider i ustaw go jako dostawcę w SavedStateHandle w
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; } } }
Aby przywrócić dane File, gdy użytkownik wróci, pobierz Bundle temp_file z SavedStateHandle. Jest to ten sam Bundle udostępniany przez saveTempFile(), który zawiera ścieżkę bezwzględną. Ścieżka bezwzględna może być następnie używana do utworzenia nowej instancji File.
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; } } }
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy język JavaScript jest wyłączony.
- Zapisywanie stanów interfejsu
- Praca z danymi dostępnymi do obserwacji
- Tworzenie ViewModel z zależnościami