Модуль сохранения состояния для ViewModel. Часть Android Jetpack .
Как упоминалось в разделе «Сохранение состояний пользовательского интерфейса» , объекты ViewModel могут обрабатывать изменения конфигурации, поэтому вам не нужно беспокоиться о состоянии при повороте экрана или в других случаях. Однако, если вам необходимо обрабатывать завершение процесса, инициированное системой, вы можете использовать API SavedStateHandle в качестве резервного варианта.
Состояние пользовательского интерфейса обычно хранится или упоминается в объектах ViewModel , поэтому использование rememberSaveable в Compose требует некоторого шаблонного кода, который модуль сохранения состояния может обработать за вас.
При использовании этого модуля объекты ViewModel получают объект SavedStateHandle через свой конструктор. Этот объект представляет собой карту ключ-значение, которая позволяет записывать и извлекать объекты из сохраненного состояния. Эти значения сохраняются после завершения процесса системой и остаются доступными через тот же объект.
Сохраненное состояние привязано к стеку задач. Если стек задач исчезает, то и сохраненное состояние также исчезает. Это может произойти при принудительной остановке приложения, удалении приложения из меню последних приложений или перезагрузке устройства. В таких случаях стек задач исчезает, и вы не можете восстановить информацию из сохраненного состояния. В сценариях закрытия состояния пользовательского интерфейса по инициативе пользователя сохраненное состояние не восстанавливается. В сценариях, инициированных системой , оно восстанавливается.
Настраивать
Чтобы использовать SavedStateHandle , примите его в качестве аргумента конструктора вашей ViewModel .
class SavedStateViewModel(private val state: SavedStateHandle) : ViewModel() { ... }
Затем вы можете получить экземпляр вашей ViewModel в ваших компонуемых объектах без какой-либо дополнительной настройки. Фабрика ViewModel по умолчанию предоставляет соответствующий SavedStateHandle для вашей ViewModel .
class MyViewModel : ViewModel() { /*...*/ } // import androidx.lifecycle.viewmodel.compose.viewModel @Composable fun MyScreen( viewModel: MyViewModel = viewModel() ) { // use viewModel here }
При предоставлении пользовательского экземпляра ViewModelProvider.Factory вы можете включить использование SavedStateHandle , используя CreationExtras и DSL viewModelFactory .
Работа с SavedStateHandle
Класс SavedStateHandle представляет собой карту типа «ключ-значение», которая позволяет записывать и извлекать данные из сохраненного состояния с помощью методов set() и get() .
Использование SavedStateHandle позволяет сохранить значение запроса после завершения процесса, гарантируя, что пользователь увидит тот же набор отфильтрованных данных до и после повторного запуска, без необходимости вручную сохранять, восстанавливать и передавать это значение обратно в ViewModel для активности или фрагмента.
SavedStateHandle также имеет другие методы, которые вы можете ожидать при взаимодействии с картой ключ-значение:
-
contains(String key)- Проверяет, существует ли значение для заданного ключа. -
remove(String key)- Удаляет значение для заданного ключа. -
keys()- Возвращает все ключи, содержащиеся вSavedStateHandle.
Кроме того, вы можете получать значения из SavedStateHandle , используя Observable Data Holder. Список поддерживаемых типов включает следующее:
StateFlow
Вы можете получать значения из SavedStateHandle обернутого в наблюдаемый объект StateFlow . В зависимости от того, нужно ли вам изменять значение напрямую, вы можете выбрать поток только для чтения или изменяемый поток:
-
getStateFlow(): Используйте этот метод, если вам нужно только прочитать состояние. Когда вы обновляете значение ключа в другом местеSavedStateHandle, StateFlow получает новое значение. Это идеально подходит, когда вы хотите предоставить доступ к потоку только для чтения и преобразовать его с помощью операторов Flow. -
getMutableStateFlow(): Используйте этот метод, если вам необходим доступ как для чтения, так и для записи. Обновление.valueвозвращаемого объектаMutableStateFlowавтоматически обновляет базовый объектSavedStateHandle, избавляя вас от необходимости вручную устанавливать ключ.
Чаще всего эти значения обновляются в результате взаимодействия пользователя, например, при вводе запроса для фильтрации списка данных.
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { // Use getMutableStateFlow to read and write the query directly private val _query = savedStateHandle.getMutableStateFlow("query", "") val query: StateFlow= _query.asStateFlow() // Use getStateFlow if you only need a read-only stream to react to changes val filteredData: StateFlow<List > = query.flatMapLatest { repository.getFilteredData(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5000), initialValue = emptyList() ) fun setQuery(newQuery: String) { // Updating the MutableStateFlow automatically updates the SavedStateHandle _query.value = newQuery } }
Поддержка сериализации в KotlinX
Для работы со сложным состоянием пользовательского интерфейса можно использовать делегат saved свойств вместе с сериализацией KotlinX. Этот делегат позволяет сохранять пользовательские классы данных @Serializable непосредственно в SavedStateHandle . Это сохраняет состояние вашей ViewModel после завершения процесса, поэтому ваш Compose UI сможет беспрепятственно восстановить свое состояние при повторном запуске.
Для его использования добавьте аннотацию @Serializable к вашему классу данных и используйте saved делегат в вашей ViewModel:
import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel // Ensure you have the savedstate-ktx dependency import androidx.savedstate.serialization.saved import kotlinx.serialization.Serializable @Serializable data class UserFilterState( val searchQuery: String, val minAge: Int, val includeInactive: Boolean ) class FilterViewModel(savedStateHandle: SavedStateHandle) : ViewModel() { // The state is automatically serialized to a Bundle on process death, // and deserialized upon recreation. var filterState by savedStateHandle.saved { UserFilterState(searchQuery = "", minAge = 18, includeInactive = false) } fun updateQuery(newQuery: String) { // Mutating the property automatically updates the underlying SavedStateHandle filterState = filterState.copy(searchQuery = newQuery) } }
Поддержка состояния композиции
Если ваше состояние использует API Saver из Compose вместо сериализации KotlinX, артефакт lifecycle-viewmodel-compose предоставляет делегат saveable . Это обеспечивает совместимость между SavedStateHandle и Saver из Compose, так что любое State , которое можно сохранить с помощью rememberSaveable с пользовательским Saver также можно сохранить с помощью SavedStateHandle .
class SavedStateViewModel(private val savedStateHandle: SavedStateHandle) : ViewModel() { var filteredData: List<String> by savedStateHandle.saveable { mutableStateOf(emptyList()) } fun setQuery(query: String) { withMutableSnapshot { filteredData += query } } }
Поддерживаемые типы
Данные, хранящиеся в SavedStateHandle , сохраняются и восстанавливаются как Bundle вместе с остальными данными savedInstanceState для вашего приложения.
Типы с прямой поддержкой
По умолчанию вы можете вызывать методы set() и get() для объекта SavedStateHandle для тех же типов данных, что и Bundle , как показано ниже:
| Поддержка типов/классов | Поддержка массивов |
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+) |
Если класс не наследует ни один из перечисленных выше классов, рассмотрите возможность сделать его пригодным для использования с Parcelable, добавив аннотацию Kotlin ` @Parcelize или реализовав 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 инкапсулирует логику создания этого временного файла.
class TempFileViewModel : ViewModel() { private var tempFile: File? = null fun createOrGetTempFile(): File { return tempFile ?: File.createTempFile("temp", null).also { tempFile = it } } }
Чтобы гарантировать сохранность временного файла при завершении и последующем восстановлении процесса активности, TempFileViewModel может использовать SavedStateHandle для сохранения своих данных. Для сохранения данных TempFileViewModel необходимо реализовать интерфейс SavedStateProvider и установить его в качестве поставщика данных для SavedStateHandle ViewModel :
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 } } }
Чтобы восстановить данные File после возвращения пользователя, получите объект temp_file Bundle из SavedStateHandle . Это тот же Bundle , который предоставляет saveTempFile() и который содержит абсолютный путь. Затем абсолютный путь можно использовать для создания нового экземпляра File .
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 } } }
SavedStateHandle в тестах
Чтобы протестировать ViewModel , которая принимает SavedStateHandle в качестве зависимости, создайте новый экземпляр SavedStateHandle с необходимыми тестовыми значениями и передайте его в тестируемый экземпляр ViewModel .
class MyViewModelTest { private lateinit var viewModel: MyViewModel @Before fun setup() { val savedState = SavedStateHandle(mapOf("someIdArg" to testId)) viewModel = MyViewModel(savedState = savedState) } }
Дополнительные ресурсы
Для получения дополнительной информации о модуле Saved State для ViewModel см. следующие ресурсы.
Кодлабс
Просмотры контента
{% verbatim %}Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Сохранение состояний пользовательского интерфейса
- Работа с наблюдаемыми объектами данных
- Создавайте ViewModel с зависимостями.