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 у фрагменту или 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 или наведите диспечера у 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 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() у тест фрагменту. Према Google, unit тестови покривају 80–90% логике ViewModel без инструменталних тестова.
Закључак
Развићемо мобилну апликацију под кључ
IT Sectr креира iOS и Android апликације за стартапе и предузећа од 2017. године. Саветоваћемо вас и предложити најбоље решење.
Прочитајте такође