Модуль Saved State для ViewModel (Views) Часть Android Jetpack.
Основные понятия и реализация Jetpack Compose
Как упоминалось в разделе Сохранение состояний пользовательского интерфейса, объекты ViewModel могут обрабатывать изменения конфигурации, поэтому вам не нужно беспокоиться о состоянии при поворотах или в других случаях. Однако если вам нужно обрабатывать завершение процесса, инициированное системой, вы можете использовать API SavedStateHandle в качестве резервного.
Состояние пользовательского интерфейса обычно хранится или указывается в объектах ViewModel, а не в действиях, поэтому для использования onSaveInstanceState() требуется шаблонный код, который может обрабатывать модуль сохраненного состояния.
При использовании этого модуля объекты ViewModel получают объект SavedStateHandle через конструктор. Этот объект представляет собой карту ключ-значение, которая позволяет записывать и извлекать объекты в сохраненное состояние и из него. Эти значения сохраняются после того, как система завершает процесс, и остаются доступными через тот же объект.
Сохраненное состояние привязано к стеку задач. Если стек задач исчезнет, сохраненное состояние также исчезнет. Это может произойти, если принудительно остановить приложение, удалить его из меню недавно использованных или перезагрузить устройство. В таких случаях стек задач исчезает, и вы не можете восстановить информацию в сохраненном состоянии. В сценариях закрытия состояния пользовательского интерфейса по инициативе пользователя сохраненное состояние не восстанавливается. В сценариях, инициированных системой, – да.
Настроить
Начиная с версии Fragment 1.2.0 или ее транзитивной зависимости Activity 1.1.0, вы можете принимать SavedStateHandle в качестве аргумента конструктора для ViewModel.
Kotlin
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Java
public class SavedStateViewModel extends ViewModel { private SavedStateHandle state; public SavedStateViewModel(SavedStateHandle savedStateHandle) { state = savedStateHandle; } ... }
После этого вы сможете получить экземпляр ViewModel без дополнительной настройки. Стандартная фабрика ViewModel предоставляет подходящий экземпляр SavedStateHandle для вашего 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); ... } ... }
Если вы используете собственный экземпляр ViewModelProvider.Factory, то можете включить использование SavedStateHandle, расширив AbstractSavedStateViewModelFactory.
Работа с SavedStateHandle
Класс SavedStateHandle – это карта ключ-значение, которая позволяет записывать данные в сохраненное состояние и извлекать их из него с помощью методов set() и get().
При использовании SavedStateHandle значение запроса сохраняется при завершении процесса, благодаря чему пользователь видит тот же набор отфильтрованных данных до и после повторного создания без необходимости вручную сохранять, восстанавливать и пересылать это значение обратно в ViewModel.
В SavedStateHandle также есть другие методы, которые могут понадобиться при взаимодействии с картой пар "ключ-значение":
contains(String key)– проверяет, есть ли значение для заданного ключа.remove(String key)– удаляет значение для заданного ключа.keys()– возвращает все ключи, содержащиеся вSavedStateHandle.
Кроме того, вы можете получать значения из SavedStateHandle с помощью наблюдаемого держателя данных. Поддерживаются следующие типы:
LiveData
Получите значения из SavedStateHandle, которые заключены в наблюдаемый объект LiveData, используя getLiveData(). Когда значение ключа обновляется, LiveData получает новое значение. Чаще всего значение задается в результате действий пользователя, например ввода запроса для фильтрации списка данных. Затем это обновленное значение можно использовать для преобразования 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); } }
Поддерживаемые типы
Данные, хранящиеся в SavedStateHandle, сохраняются и восстанавливаются как Bundle вместе с остальными savedInstanceState для действия или фрагмента.
Сохранение классов, не поддерживающих Parcelable
Если класс не реализует Parcelable или Serializable и не может быть изменен для реализации одного из этих интерфейсов, то сохранить экземпляр этого класса непосредственно в SavedStateHandle невозможно.
Начиная с версии Lifecycle 2.3.0-alpha03, SavedStateHandle позволяет сохранять любой объект, предоставляя собственную логику для сохранения и восстановления объекта в виде Bundle с помощью метода setSavedStateProvider().
SavedStateRegistry.SavedStateProvider – это интерфейс, который определяет один метод saveState(), возвращающий Bundle с состоянием, которое вы хотите сохранить. Когда SavedStateHandle будет готов сохранить свое состояние, он вызовет saveState(), чтобы получить Bundle из SavedStateProvider, и сохранит Bundle для связанного ключа.
Рассмотрим пример приложения, которое запрашивает изображение из приложения камеры с помощью намерения ACTION_IMAGE_CAPTURE, передавая временный файл, в котором камера должна сохранить изображение. TempFileViewModel содержит логику для создания этого временного файла.
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; } }
Чтобы временный файл не был утерян, если процесс действия будет завершен и позже восстановлен, TempFileViewModel может использовать SavedStateHandle для сохранения данных. Чтобы разрешить TempFileViewModel сохранять данные, реализуйте SavedStateProvider и задайте его в качестве поставщика в SavedStateHandle для 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; } } }
Чтобы восстановить данные File, когда пользователь вернется, извлеките temp_file
Bundle из SavedStateHandle. Это тот же Bundle, который предоставляется saveTempFile() и содержит абсолютный путь. Затем абсолютный путь можно использовать для создания нового экземпляра 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; } } }
Рекомендуем
- Примечание. Текст ссылки показывается, когда JavaScript отключен.
- Сохранение состояний пользовательского интерфейса
- Как работать с объектами наблюдаемых данных
- Создание ViewModel с зависимостями