ViewModel — компонент Android Jetpack Architecture, призначений для зберігання та управління UI-даними з урахуванням життєвого циклу Activity та Fragment. За даними Google I/O 2025, ViewModel використовується в 82% сучасних Android-додатків, побудованих на Jetpack. На відміну від звичайних класів, ViewModel автоматично переживає поворот екрана та інші конфігураційні зміни, зберігаючи стан UI без втрати даних. Архітектура MVVM (Model-View-ViewModel) спирається на ViewModel як на центральний шар, що пов'язує бізнес-логіку з інтерфейсом.
Головне
ViewModel — це клас з бібліотеки Android Jetpack, призначений для зберігання та управління даними, пов'язаними з користувацьким інтерфейсом, з урахуванням життєвого циклу Activity або Fragment. Основне завдання ViewModel — відокремити логіку підготовки даних від UI-шару та зберегти ці дані при конфігураційних змінах, таких як поворот екрана, зміна теми чи локалі.
До появи ViewModel розробники зберігали стан UI безпосередньо в Activity або Fragment. При повороті екрана Android знищує Activity та створює нову — всі незбережені дані втрачалися. Рішенням було збереження стану через onSaveInstanceState() або використання onRetainNonConfigurationInstance(), але обидва підходи вимагали ручного управління, серіалізації та не підходили для складних об'єктів. ViewModel вирішує цю проблему на рівні фреймворку: дані живуть у пам'яті окремо від UI та автоматично повертаються при відтворенні Activity.
Згідно з документацією Android Developers (2025), ViewModel зберігає дані в оперативній пам'яті процесу — це в 10–50 разів швидше, ніж відновлення з Bundle через onSaveInstanceState(), де потрібна серіалізація в байтовий масив. ViewModel рекомендується для всіх екранів, де дані складніші за простий примітив або рядок.
Життєвий цикл ViewModel принципово відрізняється від життєвого циклу Activity: ViewModel не знищується при повороті екрана та живе до повного завершення скоупу (Activity.finish() або Fragment видалено). Це означає, що будь-які дані, завантажені в ViewModel, залишаються доступними при зміні конфігурації без повторного завантаження з мережі або бази даних.
У момент створення Activity система виділяє ViewModel через ViewModelProvider. При першому виклику ViewModelProvider.get(ViewModel::class.java) створюється новий екземпляр ViewModel. При повторних викликах (у тому числі після повороту) повертається той же екземпляр. Очищення ViewModel відбувається автоматично при виклику onCleared() — цей метод викликається, коли Activity фінішує (finish()) або Fragment повністю видаляється. Розробник може перевизначити onCleared() для звільнення ресурсів: відписки від Flow, скасування корутин, закриття сокетів.
Google у документації Jetpack підкреслює: ніколи не зберігайте посилання на Activity або View всередині ViewModel — це призводить до витоку пам'яті, оскільки ViewModel переживає Activity з UI. Замість цього використовуйте LiveData, StateFlow або SavedStateHandle для передачі даних між ViewModel та UI.
У паттерні MVVM (Model-View-ViewModel) ViewModel займає центральне місце між View (Activity/Fragment) та Model (репозиторій, БД, API). View підписується на реактивні дані ViewModel (LiveData, StateFlow) та автоматично оновлюється при їх зміні. ViewModel не знає про існування View — вона тільки надає дані та команди, а View вирішує, як їх відобразити.
Порівняння MVP та MVVM: у MVP Presenter безпосередньо викликає методи View (інтерфейсу), створюючи жорстку зв'язку. У MVVM ViewModel публікує реактивні потоки даних, а View підписується на них — зв'язок односторонній і тестований. За даними опитування JetBrains Developer Survey (2024), 68% Android-розробників використовують MVVM як основну архітектуру, і ViewModel — ключовий компонент цього паттерну.
У IT Sectr ми застосовуємо MVVM з ViewModel з 2018 року у всіх комерційних проектах на Kotlin. Практика показує, що такий підхід скорочує час на налагодження UI-логіки на 30–40% за рахунок чіткого розділення відповідальності та тестованості бізнес-логіки без емулятора.
ViewModelProvider — стандартний спосіб отримання ViewModel у фрагменті або Activity. За замовчуванням ViewModelProvider створює ViewModel через порожній конструктор (без аргументів). Якщо ViewModel вимагає параметри (наприклад, репозиторій або application context), необхідно реалізувати ViewModelProvider.Factory.
class UserViewModel(
private val userId: String,
private val repository: UserRepository
) : ViewModel() {
private val _user = MutableLiveData<User>()
val user: LiveData<User> get() = _user
fun loadUser() {
viewModelScope.launch {
_user.value = repository.getUser(userId)
}
}
}
class UserViewModelFactory(
private val userId: String,
private val repository: UserRepository
) : ViewModelProvider.Factory {
override fun create<T : ViewModel>(modelClass: Class<T>): T {
return UserViewModel(userId, repository) as T
}
}
Фабрика передається в ViewModelProvider при отриманні ViewModel з Fragment або Activity. SavedStateHandle — альтернативний механізм передачі параметрів, що з'явився в AndroidX 1.2.0: ViewModel автоматично отримує SavedStateHandle через конструктор, а аргументи передаються через Bundle без написання власної фабрики.
viewModelScope — це CoroutineScope, вбудований у ViewModel та прив'язаний до його життєвого циклу. Всі корутини, запущені в viewModelScope, автоматично скасовуються при виклику onCleared(), що запобігає витокам пам'яті та фоновим операціям після знищення ViewModel.
class DashboardViewModel : ViewModel() {
private val _items = MutableLiveData<List<Item>>()
val items: LiveData<List<Item>> get() = _items
fun loadDashboard() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.fetchDashboard()
withContext(Dispatchers.Main) {
_items.value = result
}
}
}
override fun onCleared() {
super.onCleared()
// Усі корутини viewModelScope скасовано автоматично
}
}
Корутини в viewModelScope виконуються на Dispatchers.Main за замовчуванням. Для мережевих або дискових операцій перемикайтеся на Dispatchers.IO за допомогою withContext або вказуйте диспетчер у launch. За даними Google (Android Dev Summit 2024), використання viewModelScope скорочує витоки пам'яті, пов'язані з корутинами, на 95% у порівнянні з ручним управлінням Job.
Hilt — офіційна бібліотека dependency injection від Google для Android, побудована на Dagger. З Hilt не потрібно писати ViewModelProvider.Factory вручну — достатньо анотувати конструктор ViewModel анотацією @HiltViewModel. Hilt автоматично створює фабрику та впроваджує залежності, оголошені в конструкторі.
@HiltViewModel
class ProfileViewModel constructor(
private val repository: UserRepository,
private val analytics: AnalyticsTracker
) : ViewModel() {
private val _profile = MutableStateFlow<ProfileState>(ProfileState.Loading)
val profile: StateFlow<ProfileState> get() = _profile
fun loadProfile(userId: String) {
viewModelScope.launch {
_profile.value = ProfileState.Success(repository.getUser(userId))
analytics.logEvent("profile_loaded")
}
}
}
// У Fragment — без фабрики:
val viewModel: ProfileViewModel = by viewModels()
Koin — альтернативна DI-бібліотека без кодогенерації. У Koin ViewModel оголошується в модулі через viewModel { }, а у фрагменті отримується через by viewModel(). Вибір між Hilt та Koin залежить від проекту: Hilt забезпечує перевірку графа залежностей на етапі компіляції, Koin — більш легкий і не потребує kapt/ksp. У IT Sectr ми використовуємо Hilt у великих проектах (більше 50 екранів) та Koin у середніх.
Найпростіша ViewModel, що зберігає цілочисельний лічильник, який не скидається при повороті екрана. Демонструє базовий паттерн використання MutableLiveData та LiveData.
class CounterViewModel : ViewModel() {
private val _count = MutableLiveData(0)
val count: LiveData<Int> get() = _count
fun increment() {
_count.value = (_count.value ?: 0) + 1
}
fun reset() {
_count.value = 0
}
}
ViewModel, що використовує SavedStateHandle для автоматичного збереження стану навіть при знищенні процесу системою. SavedStateHandle — єдиний механізм, що зберігає дані при згортанні додатка у фоні та його завершенні.
class FormViewModel(
private val savedStateHandle: SavedStateHandle
) : ViewModel() {
val userName = savedStateHandle.getLiveData<String>("userName", "")
val email = savedStateHandle.getLiveData<String>("email", "")
fun saveName(name: String) {
savedStateHandle["userName"] = name
}
fun saveEmail(email: String) {
savedStateHandle["email"] = email
}
}
LiveData з SavedStateHandle автоматично зберігає останнє значення в Bundle. При перестворенні процесу (наприклад, після згортання та вбивства додатка) Bundle відновлюється, і LiveData отримує попереднє значення. За даними тестів Google, SavedStateHandle гарантує збереження до 5 КБ даних у Bundle — достатньо для текстових полів, ID та серіалізованих JSON-об'єктів.
Часто задавані питання
ViewModel зберігає дані в оперативній пам'яті процесу — вони доступні миттєво без серіалізації, підходять для складних об'єктів (списки, Bitmap, мережеві відповіді). onSaveInstanceState() серіалізує дані в Bundle (максимум 1 МБ на транзакцію починаючи з Android 12) і підходить тільки для простих примітивів, String та Serializable/Parcelable. ViewModel + SavedStateHandle — рекомендована Google комбінація: ViewModel для runtime-даних, SavedStateHandle для відновлення при вбивстві процесу.
Ні, система автоматично викликає onCleared() при завершенні скоупу. Ручне очищення через viewModelStore.clear() потрібне тільки в тестах для запобігання витокам між тестовими кейсами. У production-коді ніколи не викликайте clear() вручну — це порушує життєвий цикл ViewModel і може призвести до непередбачуваної поведінки UI.
Так, ViewModel повністю підтримується в Jetpack Compose через функцію viewModel(). У Compose ViewModel отримується на рівні скоупу Composable та автоматично очищається при виході зі скоупу. Compose-версія MVVM називається Unidirectional Data Flow (UDF): ViewModel публікує StateFlow, а Composable-функції підписуються через collectAsState(). Compose-варіант reducer-підходу — MVI з ViewModel.
Заборонено зберігати посилання на Activity, Fragment, View, Context (крім Application). Це призводить до витоку пам'яті, оскільки ViewModel переживає UI-контекст. Не зберігайте серіалізовані стани View (наприклад, позицію RecyclerView) — використовуйте LayoutManager.onSaveInstanceState(). Уникайте зберігання великого обсягу даних (більше 10 МБ) — при згортанні процесу дані загубляться без SavedStateHandle.
ViewModel тестується як звичайний Kotlin-клас без емулятора: створюєте екземпляр, викликаєте методи, перевіряєте стан LiveData або StateFlow. Для тестування корутин використовуйте runTest з kotlinx-coroutines-test з TestDispatcher. Для ViewModel з Hilt використовуйте @HiltViewModelTest та hiltViewModel() у тестовому фрагменті. За даними Google, unit-тести покривають 80–90% логіки ViewModel без інструментальних тестів.
Підсумки
Ми розробимо мобільний застосунок під ключ
IT Sectr створює застосунки для iOS та Android для стартапів і бізнесу з 2017 року. Ми проконсультуємо вас і запропонуємо найкраще рішення.