ViewModel — какво е, управление на UI данни в Android Jetpack

Автор: IT Sectr Публикувано: 2026-02-19 Време за четене: 9 мин

ViewModel — компонент на Android Jetpack Architecture, предназначен за съхранение и управление на UI данни, като се взема предвид жизнения цикъл на Activity и Fragment. Според Google I/O 2025, ViewModel се използва в 82% от съвременните Android приложения, изградени на Jetpack. За разлика от обикновените класове, ViewModel автоматично издържа на завъртане на екрана и други конфигурационни промени, запазвайки състоянието на UI без загуба на данни. Архитектурата MVVM (Model-View-ViewModel) разчита на ViewModel като централен слой, свързващ бизнес логиката с интерфейса.

Основни точки

  • ViewModel — Jetpack компонент за съхранение на UI данни, устойчив на завъртане на екрана и повторно създаване на Activity.
  • Жизненият цикъл на ViewModel е обвързан с scope (Activity/Fragment/Composable), а не с отделен екземпляр на Activity.
  • viewModelScope — вградена корутина вътре в ViewModel, автоматично отменяна при почистване на ViewModel.
  • ViewModelProvider — фабрика за създаване на ViewModel с поддръжка на dependency injection чрез Hilt или Koin.
  • В MVVM ViewModel замества presenter от MVP, премахвайки обвързването с конкретен View чрез LiveData или StateFlow.

Какво е ViewModel в Android?

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 принципно се различава от жизнения цикъл на 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.

ViewModel в архитектурата MVVM

В шаблона 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 и фабрики: създаване с параметри

ViewModelProvider — стандартният начин за получаване на ViewModel във fragment или Activity. По подразбиране ViewModelProvider създава ViewModel чрез празен конструктор (без аргументи). Ако ViewModel изисква параметри (например репозиторий или контекст на приложението), трябва да се имплементира ViewModelProvider.Factory.

kotlin
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 и корутини в ViewModel

viewModelScope — CoroutineScope, вграден в ViewModel и обвързан с неговия жизнен цикъл. Всички корутини, стартирани в viewModelScope, автоматично се отменят при извикване на onCleared(), което предотвратява изтичане на памет и фонови операции след унищожаване на ViewModel.

kotlin
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.

ViewModel с Hilt и Koin: DI подходи

Hilt — официалната библиотека dependency injection от Google за Android, изградена върху Dagger. С Hilt не е необходимо да пишете ViewModelProvider.Factory ръчно — достатъчно е да анотирате конструктора на ViewModel с анотация @HiltViewModel. Hilt автоматично създава фабриката и инжектира зависимостите, декларирани в конструктора.

kotlin
@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 в Kotlin

Пример 1: Основна ViewModel с брояч

Най-простата ViewModel, която съхранява целочислен брояч, който не се нулира при завъртане на екрана. Демонстрира основния модел на използване на MutableLiveData и LiveData.

kotlin
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
    }
}

Пример 2: ViewModel с SavedStateHandle

ViewModel, която използва SavedStateHandle за автоматично запазване на състоянието дори при унищожаване на процеса от системата. SavedStateHandle е единственият механизъм, който запазва данни при минимизиране на приложението на заден фон и неговото прекратяване.

kotlin
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 се различава от onSaveInstanceState?

ViewModel съхранява данни в RAM паметта на процеса — те са достъпни незабавно без сериализация, подходящи за сложни обекти (списъци, Bitmap, мрежови отговори). onSaveInstanceState() сериализира данни в Bundle (максимум 1 MB на транзакция от Android 12) и е подходящ само за прости примитиви, String и Serializable/Parcelable. ViewModel + SavedStateHandle — препоръчана от Google комбинация: ViewModel за runtime данни, SavedStateHandle за възстановяване при убиване на процеса.

Трябва ли да почистваме ViewModel ръчно?

Не, системата автоматично извиква onCleared() при приключване на scope. Ръчно почистване чрез viewModelStore.clear() се изисква само в тестове за предотвратяване на изтичания между тестови случаи. В production код никога не извиквайте clear() ръчно — това нарушава жизнения цикъл на ViewModel и може да доведе до непредвидимо поведение на UI.

Може ли ViewModel да се използва в Compose?

Да, ViewModel се поддържа напълно в Jetpack Compose чрез функцията viewModel(). В Compose ViewModel се получава на ниво scope на Composable и автоматично се почиства при напускане на scope. Compose версията на MVVM се нарича Unidirectional Data Flow (UDF): ViewModel публикува StateFlow, а Composable функциите се абонират чрез collectAsState(). Compose вариантът на reducer подхода — MVI с ViewModel.

Какво не трябва да се съхранява в ViewModel?

Забранено е съхраняването на препратки към Activity, Fragment, View, Context (освен Application). Това води до изтичане на памет, тъй като ViewModel живее по-дълго от UI контекста. Не съхранявайте сериализирани състояния на View (например позиция на RecyclerView) — използвайте LayoutManager.onSaveInstanceState(). Избягвайте съхраняване на голямо количество данни (над 10 MB) — при минимизиране на процеса данните се губят без SavedStateHandle.

Как да тестваме ViewModel?

ViewModel се тества като обикновен Kotlin клас без емулатор: създавате екземпляр, извиквате методи, проверявате състоянието на LiveData или StateFlow. За тестване на корутини използвайте runTest от kotlinx-coroutines-test с TestDispatcher. За ViewModel с Hilt използвайте @HiltViewModelTest и hiltViewModel() в тестовия fragment. Според Google, unit тестовете покриват 80–90% от логиката на ViewModel без инструментални тестове.

Обобщение

  • ViewModel — Jetpack компонент за съхранение на UI данни, който издържа на конфигурационни промени без загуба на състояние.
  • Жизненият цикъл на ViewModel е обвързан с scope (Activity/Fragment), а не с екземпляр на Activity — почистването става при приключване на scope.
  • ViewModelProvider — фабричен метод за създаване на ViewModel; за параметри се имплементира ViewModelProvider.Factory.
  • viewModelScope — вграден CoroutineScope, автоматично отменя корутините при onCleared(), което елиминира изтичания на памет.
  • SavedStateHandle — механизъм за запазване на състояние при убиване на процеса, интегрируем в конструктора на ViewModel.
  • Hilt и @HiltViewModel — стандартен DI метод за ViewModel в големи проекти; Koin — лека алтернатива без генериране на код.
  • ViewModel — основата на MVVM и UDF архитектурите, използвана в 82% от Jetpack приложенията според Google I/O 2025.

Ще разработим мобилно приложение под ключ

IT Sectr създава iOS и Android приложения за стартъпи и бизнеси от 2017 г. Ще ви консултираме и ще предложим най-доброто решение.

Обсъдете проекта

Прочетете също