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 съхранява данни в RAM паметта на процеса — това е 10–50 пъти по-бързо от възстановяване от Bundle чрез onSaveInstanceState(), където се изисква сериализация до байтов масив. ViewModel се препоръчва за всички екрани, където данните са по-сложни от прост примитив или низ.
Жизненият цикъл на ViewModel принципно се различава от жизнения цикъл на Activity: ViewModel не се унищожава при завъртане на екрана и живее до пълното приключване на scope (Activity.finish() или Fragment removed). Това означава, че всички данни, заредени в 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 във fragment или Activity. По подразбиране ViewModelProvider създава ViewModel чрез празен конструктор (без аргументи). Ако ViewModel изисква параметри (например репозиторий или контекст на приложението), трябва да се имплементира 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 или посочете dispatcher в 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 { }, а във fragment се получава чрез 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 KB данни в Bundle — достатъчно за текстови полета, ID-та и сериализирани JSON обекти.
Често задавани въпроси
ViewModel съхранява данни в RAM паметта на процеса — те са достъпни незабавно без сериализация, подходящи за сложни обекти (списъци, Bitmap, мрежови отговори). onSaveInstanceState() сериализира данни в Bundle (максимум 1 MB на транзакция от Android 12) и е подходящ само за прости примитиви, String и Serializable/Parcelable. ViewModel + SavedStateHandle — препоръчана от Google комбинация: ViewModel за runtime данни, SavedStateHandle за възстановяване при убиване на процеса.
Не, системата автоматично извиква onCleared() при приключване на scope. Ръчно почистване чрез viewModelStore.clear() се изисква само в тестове за предотвратяване на изтичания между тестови случаи. В production код никога не извиквайте clear() ръчно — това нарушава жизнения цикъл на ViewModel и може да доведе до непредвидимо поведение на UI.
Да, ViewModel се поддържа напълно в Jetpack Compose чрез функцията viewModel(). В Compose ViewModel се получава на ниво scope на Composable и автоматично се почиства при напускане на scope. 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 MB) — при минимизиране на процеса данните се губят без SavedStateHandle.
ViewModel се тества като обикновен Kotlin клас без емулатор: създавате екземпляр, извиквате методи, проверявате състоянието на LiveData или StateFlow. За тестване на корутини използвайте runTest от kotlinx-coroutines-test с TestDispatcher. За ViewModel с Hilt използвайте @HiltViewModelTest и hiltViewModel() в тестовия fragment. Според Google, unit тестовете покриват 80–90% от логиката на ViewModel без инструментални тестове.
Обобщение
Ще разработим мобилно приложение под ключ
IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.
Прочетете също