Moduł Zapisany stan dla ViewModel Część stanowiąca część Androida Jetpack.
Jak wspomnieliśmy w sekcji Zapisywanie stanów interfejsu, obiekty ViewModel
mogą obsługiwać zmiany konfiguracji, więc nie musisz się martwić o stan w rotacjach ani w innych przypadkach. Jeśli jednak musisz radzić sobie ze śmiercią procesów inicjowanych przez system, możesz użyć interfejsu API SavedStateHandle
jako opcji zapasowej.
Stan interfejsu użytkownika jest zwykle przechowywany lub do którego odwołują się obiekty ViewModel
, a nie aktywności, więc użycie onSaveInstanceState()
lub rememberSaveable
wymaga pewnego schematu, który może obsługiwać zapisany moduł stanu.
Gdy używany jest ten moduł, obiekty ViewModel
otrzymują obiekt SavedStateHandle
za pomocą swojego konstruktora. Ten obiekt jest mapą klucz-wartość, która umożliwia zapisywanie obiektów w zapisanym stanie i ich pobieranie. Wartości te pozostają aktywne po zakończeniu procesu przez system i pozostają dostępne w tym samym obiekcie.
Zapisany stan jest powiązany ze stosem zadań. Jeśli stos zadań zniknie, zapisany stan również zniknie. Może się to zdarzyć, gdy wymuszasz zatrzymanie aplikacji, została ona usunięta z menu ostatnich lub gdy urządzenie zostało ponownie uruchomione. W takich przypadkach stos zadań zniknie i nie będzie można przywrócić informacji w zapisanym stanie. W scenariuszach odrzucania stanu interfejsu inicjowanego przez użytkownika zapisany stan nie jest przywracany. W sytuacjach inicjowanych przez system tak właśnie jest.
Skonfiguruj
Począwszy od fragmentu 1.2.0 lub jego pośredniej zależności, czynności 1.1.0 możesz akceptować SavedStateHandle
jako argument konstruktora dla ViewModel
.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
Potem możesz pobrać instancję ViewModel
bez dodatkowej konfiguracji. Domyślna fabryka ViewModel
udostępnia na urządzeniu ViewModel
odpowiednie
SavedStateHandle
.
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); ... } ... }
Udostępniając niestandardową instancję ViewModelProvider.Factory
, możesz włączyć użycie SavedStateHandle
, rozszerzając zakres AbstractSavedStateViewModelFactory
.
Praca z SavedStateHandle
Klasa SavedStateHandle
to mapa klucz-wartość, która umożliwia zapisywanie i pobieranie danych do zapisanego stanu i z niego za pomocą metod set()
i get()
.
Gdy używany jest SavedStateHandle
, wartość zapytania jest zachowywana przez cały proces zakończenia procesu, dzięki czemu użytkownik widzi ten sam zbiór odfiltrowanych danych przed i po użyciu funkcji, bez konieczności ręcznego zapisywania, przywracania i przekazywania tej wartości do funkcji ViewModel
.
SavedStateHandle
oferuje też inne metody, których możesz się spodziewać podczas interakcji z mapą klucz-wartość:
contains(String key)
– sprawdza, czy dla danego klucza istnieje wartość.remove(String key)
– usuwa wartość danego klucza.keys()
– zwraca wszystkie klucze zawarte w elemencieSavedStateHandle
.
Dodatkowo możesz pobierać wartości z funkcji SavedStateHandle
za pomocą możliwego do obserwacji właściciela danych. Lista obsługiwanych typów:
LiveData,
Pobierz z SavedStateHandle
wartości, które są umieszczone w obiekcie LiveData
możliwym do obserwacji za pomocą getLiveData()
.
Po zaktualizowaniu wartości klucza LiveData
otrzyma nową wartość. Wartość jest często ustawiana na podstawie interakcji użytkownika, takich jak wpisanie zapytania w celu odfiltrowania listy danych. Za pomocą zaktualizowanej wartości możesz przekształcić element 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); } }
StateFlow,
Pobierz z SavedStateHandle
wartości, które są umieszczone w obiekcie StateFlow
możliwym do obserwacji za pomocą getStateFlow()
.
Po zaktualizowaniu wartości klucza StateFlow
otrzyma nową wartość. Wartość ta jest często ustawiana na podstawie interakcji użytkownika, takich jak wpisanie zapytania w celu filtrowania listy danych. Następnie możesz przekształcić tę zaktualizowaną wartość za pomocą innych operatorów przepływu.
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 } }
Obsługa stanu tworzenia wiadomości eksperymentalnych
Artefakt lifecycle-viewmodel-compose
udostępnia eksperymentalne interfejsy API saveable
umożliwiające interoperacyjność między interfejsami SavedStateHandle
i Saver
. Dzięki temu wszystkie adresy State
, które możesz zapisać za pomocą rememberSaveable
z niestandardową wartością Saver
, można zapisać w SavedStateHandle
.
Kotlin
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Obsługiwane typy
Dane przechowywane w elemencie SavedStateHandle
są zapisywane i przywracane jako Bundle
razem z resztą savedInstanceState
na potrzeby aktywności lub fragmentu.
Typy obsługiwane bezpośrednio
Domyślnie możesz wywołać set()
i get()
w SavedStateHandle
dla tych samych typów danych co Bundle
, jak pokazano poniżej:
Obsługa typu/klasy | Obsługa tablicy |
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+) |
Jeśli klasa nie rozszerza żadnego z elementów z listy powyżej, rozważ przekształcenie jej w funkcję parcelową przez dodanie adnotacji Kotlin @Parcelize
lub bezpośrednie wdrożenie Parcelable
.
Zapisywanie klas bez możliwości wynajęcia
Jeśli klasa nie implementuje Parcelable
ani Serializable
i nie można jej zmodyfikować w celu zaimplementowania jednego z tych interfejsów, nie można bezpośrednio zapisać instancji tej klasy w SavedStateHandle
.
Począwszy od cyklu życia 2.3.0-alpha03, SavedStateHandle
umożliwia zapisywanie dowolnego obiektu, udostępniając własną logikę zapisu i przywracania obiektu jako Bundle
przy użyciu metody setSavedStateProvider()
. SavedStateRegistry.SavedStateProvider
to interfejs określający pojedynczą metodę saveState()
zwracającą obiekt Bundle
zawierający stan, który chcesz zapisać. Gdy SavedStateHandle
jest gotowy do zapisania swojego stanu, wywołuje metodę saveState()
, aby pobrać Bundle
z SavedStateProvider
i zapisuje Bundle
dla powiązanego klucza.
Przeanalizujmy przykład aplikacji, która za pomocą intencji ACTION_IMAGE_CAPTURE
wysyła do aplikacji aparatu żądanie udostępnienia obrazu i przekazuje plik tymczasowy miejsca, w którym kamera ma zapisać obraz. Pole 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 plik tymczasowy nie został utracony, gdy proces aktywności zostanie przerwany, a później przywrócony, TempFileViewModel
może zachować dane za pomocą metody SavedStateHandle
. Aby zezwolić usłudze TempFileViewModel
na zapisywanie danych, zaimplementuj SavedStateProvider
i ustaw ją jako dostawcę w SavedStateHandle
elementu 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
po powrocie użytkownika, pobierz temp_file
Bundle
z SavedStateHandle
. Jest to ta sama wartość Bundle
podana przez funkcję saveTempFile()
, która zawiera ścieżkę bezwzględną. Ścieżkę bezwzględną można następnie użyć do utworzenia instancji nowego obiektu 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; } } }
SavedStateHandle w testach
Aby przetestować ViewModel
, który wymaga SavedStateHandle
jako zależności, utwórz nową instancję SavedStateHandle
z wymaganymi wartościami testowymi i przekaż ją do testowanej instancji ViewModel
.
Kotlin
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Dodatkowe materiały
Więcej informacji o module Zapisanego stanu dla ViewModel
znajdziesz w tych materiałach.
Ćwiczenia z programowania
Polecane dla Ciebie
- Uwaga: tekst linku jest wyświetlany, gdy JavaScript jest wyłączony
- Zapisywanie stanów interfejsu
- Praca z obserwowalnymi obiektami danych
- Tworzenie modeli widoków z zależnościami