Обзор ViewModel. Часть Android Jetpack .
Класс ViewModel — это хранилище бизнес-логики или состояния на уровне экрана . Он предоставляет доступ к состоянию пользовательскому интерфейсу и инкапсулирует связанную с ним бизнес-логику. Его главное преимущество заключается в том, что он кэширует состояние и сохраняет его при изменении конфигурации. Это означает, что вашему пользовательскому интерфейсу не нужно повторно получать данные при переходе между активностями или при изменении конфигурации, например, при повороте экрана.
Для получения дополнительной информации о владельцах состояний см. руководство по владельцам состояний . Аналогично, для получения дополнительной информации об уровне пользовательского интерфейса в целом см. руководство по уровню пользовательского интерфейса .
Преимущества ViewModel
Альтернативой ViewModel является обычный класс, хранящий данные, отображаемые в пользовательском интерфейсе. Это может стать проблемой при навигации между активностями или пунктами навигации. В этом случае данные уничтожаются, если не использовать механизм сохранения состояния экземпляра . ViewModel предоставляет удобный API для сохранения данных, который решает эту проблему.
В качестве альтернативы, для классов, сохраняющих только состояние, Compose предлагает возможности retain , позволяющие обычным классам сохранять изменения конфигурации без полной инфраструктуры ViewModel. Хотя оба механизма помогают в сохранении состояния, как правило, безопаснее предоставлять ViewModel сохраняемому экземпляру, чем наоборот, поскольку их жизненные циклы и поведение при очистке различаются.
Основные преимущества класса ViewModel заключаются, по сути, в двух:
- Это позволяет сохранять состояние пользовательского интерфейса.
- Это обеспечивает доступ к бизнес-логике.
Упорство
ViewModel обеспечивает сохранение как состояния, хранящегося в ViewModel, так и операций, которые запускает ViewModel. Такое кэширование означает, что вам не нужно повторно получать данные при распространенных изменениях конфигурации, таких как поворот экрана.
Объем
При создании экземпляра ViewModel вы передаете ему объект, реализующий интерфейс ViewModelStoreOwner . Это может быть объект навигации, граф навигации, активность или любой другой тип, реализующий этот интерфейс. Вы также можете напрямую привязать ViewModel к компонуемому объекту, используя API rememberViewModelStoreOwner . В этом случае ваша ViewModel будет привязана к жизненному циклу ViewModelStoreOwner . Она остается в памяти до тех пор, пока ее ViewModelStoreOwner не будет удален навсегда (например, когда владелец компонуемого объекта выйдет из композиции).
Ряд классов являются прямыми или косвенными подклассами интерфейса ViewModelStoreOwner . Прямыми подклассами являются ComponentActivity и NavBackStackEntry . Полный список косвенных подклассов см. в справочнике ViewModelStoreOwner . Чтобы ограничить область действия ViewModel отдельными элементами в LazyList или Pager , используйте rememberViewModelStoreProvider() , чтобы перенести управление владельцем к родительскому компоненту.
Когда в хост-активности происходит изменение конфигурации, асинхронная работа продолжается в ViewModel, независимо от того, ограничена ли она активностью или конкретным составным элементом. В этом и заключается ключ к обеспечению персистентности.
Для получения дополнительной информации см. раздел « Жизненный цикл ViewModel» , API для определения области видимости ViewModel и руководство по поднятию состояния в Jetpack Compose.
SavedStateHandle
SavedStateHandle позволяет сохранять данные не только при изменении конфигурации, но и после завершения процесса. То есть, он позволяет сохранять состояние пользовательского интерфейса неизменным, даже когда пользователь закрывает приложение и открывает его позже.
Для получения дополнительной информации о сохранении состояния пользовательского интерфейса см. раздел «Сохранение состояния пользовательского интерфейса в Compose» .
Доступ к бизнес-логике
Несмотря на то, что подавляющее большинство бизнес-логики находится на уровне данных, уровень пользовательского интерфейса также может содержать бизнес-логику. Это может быть, например, при объединении данных из нескольких хранилищ для создания состояния пользовательского интерфейса экрана, или когда определенный тип данных не требует уровня данных.
ViewModel — это подходящее место для обработки бизнес-логики на уровне пользовательского интерфейса. ViewModel также отвечает за обработку событий и делегирование их другим уровням иерархии, когда необходимо применить бизнес-логику для изменения данных приложения.
Реализуйте ViewModel.
Ниже приведён пример реализации ViewModel для экрана, позволяющего пользователю бросать игральные кости.
data class DiceUiState(
val firstDieValue: Int? = null,
val secondDieValue: Int? = null,
val numberOfRolls: Int = 0,
)
class DiceRollViewModel : ViewModel() {
// Expose screen UI state
private val _uiState = MutableStateFlow(DiceUiState())
val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()
// Handle business logic
fun rollDice() {
_uiState.update { currentState ->
currentState.copy(
firstDieValue = Random.nextInt(from = 1, until = 7),
secondDieValue = Random.nextInt(from = 1, until = 7),
numberOfRolls = currentState.numberOfRolls + 1,
)
}
}
}
Затем вы можете получить доступ к ViewModel из составного элемента на уровне экрана следующим образом:
import androidx.lifecycle.viewmodel.compose.viewModel
// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
viewModel: DiceRollViewModel = viewModel()
) {
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
// Update UI elements
}
Используйте сопрограммы с ViewModel
ViewModel включает поддержку сопрограмм Kotlin. Она способна сохранять асинхронную работу так же, как и состояние пользовательского интерфейса.
Для получения дополнительной информации см. раздел «Использование сопрограмм Kotlin с компонентами архитектуры Android» .
Жизненный цикл ViewModel
Жизненный цикл ViewModel напрямую связан с его областью видимости. ViewModel остается в памяти до тех пор, пока не исчезнет ViewModelStoreOwner к которому она относится. Это может произойти в следующих контекстах:
- В случае какой-либо деятельности — когда она завершается.
- В случае элемента навигации, когда он удаляется из стека возврата.
- В случае составного объекта, при выходе из композиции, вы можете использовать
rememberViewModelStoreOwner, чтобы ограничить область действия ViewModel произвольной частью вашего пользовательского интерфейса (например,PagerилиLazyList).
Благодаря этому ViewModels — отличное решение для хранения данных, которые сохраняются при изменении конфигурации.
На рисунке 1 показаны различные состояния жизненного цикла действия, включая его вращение и завершение. На иллюстрации также показано время жизни ViewModel рядом с соответствующим жизненным циклом действия. Данная диаграмма иллюстрирует состояния действия.

Обычно вы запрашиваете ViewModel при первом вызове системой метода onCreate() объекта Activity. Система может вызывать onCreate() несколько раз в течение существования Activity, например, при повороте экрана устройства. ViewModel существует с момента первого запроса ViewModel до завершения и уничтожения Activity.
Очистка зависимостей ViewModel
Метод onCleared вызывается ViewModel, когда ViewModelStoreOwner уничтожает его в ходе жизненного цикла. Это позволяет очистить все ресурсы и зависимости, следующие за жизненным циклом ViewModel.
Следующий пример демонстрирует альтернативу viewModelScope . viewModelScope — это встроенная область CoroutineScope , которая автоматически следует жизненному циклу ViewModel. ViewModel использует её для запуска бизнес-операций. Если вы хотите использовать пользовательскую область видимости вместо viewModelScope для упрощения тестирования , ViewModel может получить CoroutineScope в качестве зависимости в своём конструкторе. Когда ViewModelStoreOwner очищает ViewModel в конце её жизненного цикла, ViewModel также отменяет CoroutineScope .
class MyViewModel(
private val coroutineScope: CoroutineScope =
CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {
// Other ViewModel logic ...
override fun onCleared() {
coroutineScope.cancel()
}
}
Начиная с версии жизненного цикла 2.5 и выше, вы можете передать один или несколько объектов Closeable в конструктор ViewModel, которые автоматически закрываются при очистке экземпляра ViewModel.
class CloseableCoroutineScope(
context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
override val coroutineContext: CoroutineContext = context
override fun close() {
coroutineContext.cancel()
}
}
class MyViewModel(
private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
// Other ViewModel logic ...
}
Передовые методы
Ниже приведены несколько ключевых рекомендаций, которым следует следовать при внедрении ViewModel:
- Из-за их ограниченной области видимости используйте ViewModel в качестве деталей реализации держателя состояния на уровне экрана. Не используйте их в качестве держателей состояния для многократно используемых компонентов пользовательского интерфейса, таких как группы элементов или формы. В противном случае вы получите один и тот же экземпляр ViewModel в разных случаях использования одного и того же компонента пользовательского интерфейса под одним и тем же ViewModelStoreOwner, если не используете явный ключ ViewModel для каждого элемента.
- ViewModel не должны знать о деталях реализации пользовательского интерфейса. Названия методов, предоставляемых API ViewModel, и полей состояния пользовательского интерфейса должны быть максимально общими. Таким образом, ваша ViewModel сможет поддерживать любой тип пользовательского интерфейса: мобильный телефон, складной ноутбук, планшет или даже Chromebook!
- Поскольку ViewModels потенциально могут существовать дольше, чем
ViewModelStoreOwner, им не следует хранить ссылки на API, связанные с жизненным циклом, такие какContextилиResources, чтобы предотвратить утечки памяти. - Не передавайте ViewModel другим классам, функциям или другим компонентам пользовательского интерфейса. Поскольку платформа управляет ими, размещайте их как можно ближе к ней — рядом с Activity, компонуемой функцией уровня экрана или целевым элементом навигации. Это предотвратит доступ компонентов нижнего уровня к большему объему данных и логики, чем им необходимо.
Дополнительная информация
По мере усложнения данных вы можете предпочесть использовать отдельный класс только для их загрузки. Цель ViewModel — инкапсулировать данные для контроллера пользовательского интерфейса, чтобы данные сохранялись при изменении конфигурации. Информацию о загрузке, сохранении и управлении данными при изменении конфигурации см. в разделе «Сохраненные состояния пользовательского интерфейса» .
В руководстве по архитектуре приложений Android предлагается создать класс репозитория для обработки этих функций.
Дополнительные ресурсы
Для получения дополнительной информации о классе ViewModel обратитесь к следующим ресурсам.
Документация
- слой пользовательского интерфейса
- События пользовательского интерфейса
- Владельцы штатов и штат Иллинойс
- Государственное производство
- Слой данных
Просмотры контента
Образцы
Рекомендуем вам
- Примечание: текст ссылки отображается, когда JavaScript отключен.
- Используйте сопрограммы Kotlin с компонентами, учитывающими жизненный цикл.
- Сохранение состояний пользовательского интерфейса
- Загрузка и отображение постраничных данных
