ViewModel — co to jest, zarządzanie danymi UI w Android Jetpack

Autor: IT Sectr Opublikowano: 2026-02-19 Czas czytania: 9 min

ViewModel — komponent Android Jetpack Architecture przeznaczony do przechowywania i zarządzania danymi UI z uwzględnieniem cyklu życia Activity i Fragment. Według Google I/O 2025, ViewModel jest używany w 82% nowoczesnych aplikacji Android zbudowanych na Jetpack. W przeciwieństwie do zwykłych klas, ViewModel automatycznie przetrzymuje obrót ekranu i inne zmiany konfiguracji, zachowując stan UI bez utraty danych. Architektura MVVM (Model-View-ViewModel) opiera się na ViewModel jako centralnej warstwie łączącej logikę biznesową z interfejsem.

Najważniejsze

  • ViewModel — komponent Jetpack do przechowywania danych UI, odporny na obrót ekranu i ponowne tworzenie Activity.
  • Cykl życia ViewModel jest powiązany ze scope (Activity/Fragment/Composable), a nie z pojedynczą instancją Activity.
  • viewModelScope — wbudowana korutyna wewnątrz ViewModel, automatycznie anulowana przy czyszczeniu ViewModel.
  • ViewModelProvider — fabryka do tworzenia ViewModel z obsługą dependency injection przez Hilt lub Koin.
  • W MVVM ViewModel zastępuje presenter z MVP, eliminując przywiązanie do konkretnego View przez LiveData lub StateFlow.

Czym jest ViewModel w Android?

ViewModel — to klasa z biblioteki Android Jetpack przeznaczona do przechowywania i zarządzania danymi związanymi z interfejsem użytkownika, z uwzględnieniem cyklu życia Activity lub Fragment. Głównym zadaniem ViewModel jest oddzielenie logiki przygotowania danych od warstwy UI i zachowanie tych danych przy zmianach konfiguracji, takich jak obrót ekranu, zmiana motywu lub lokalizacji.

Przed pojawieniem się ViewModel programiści przechowywali stan UI bezpośrednio w Activity lub Fragment. Przy obrocie ekranu Android niszczy Activity i tworzy nowe — wszystkie niezapisane dane były tracone. Rozwiązaniem było zapisywanie stanu przez onSaveInstanceState() lub użycie onRetainNonConfigurationInstance(), ale oba podejścia wymagały ręcznego zarządzania, serializacji i nie nadawały się do złożonych obiektów. ViewModel rozwiązuje ten problem na poziomie frameworka: dane żyją w pamięci oddzielnie od UI i automatycznie wracają przy odtworzeniu Activity.

Według dokumentacji Android Developers (2025), ViewModel przechowuje dane w pamięci RAM procesu — to 10–50 razy szybciej niż odtwarzanie z Bundle przez onSaveInstanceState(), które wymaga serializacji do tablicy bajtów. ViewModel jest zalecany dla wszystkich ekranów, gdzie dane są bardziej złożone niż prosty prymityw lub ciąg znaków.

Cykl życia ViewModel: czym różni się od Activity

Cykl życia ViewModel zasadniczo różni się od cyklu życia Activity: ViewModel nie jest niszczony przy obrocie ekranu i żyje do całkowitego zakończenia scope (Activity.finish() lub Fragment removed). Oznacza to, że wszelkie dane załadowane do ViewModel pozostają dostępne przy zmianie konfiguracji bez ponownego ładowania z sieci lub bazy danych.

W momencie tworzenia Activity system przydziela ViewModel przez ViewModelProvider. Przy pierwszym wywołaniu ViewModelProvider.get(ViewModel::class.java) tworzona jest nowa instancja ViewModel. Przy kolejnych wywołaniach (w tym po obrocie) zwracana jest ta sama instancja. Czyszczenie ViewModel następuje automatycznie przy wywołaniu onCleared() — ta metoda jest wywoływana, gdy Activity jest kończone (finish()) lub Fragment jest całkowicie usuwany. Deweloper może nadpisać onCleared() w celu zwolnienia zasobów: wypisania się z Flow, anulowania korutyn, zamknięcia socketów.

Google w dokumentacji Jetpack podkreśla: nigdy nie przechowuj referencji do Activity lub View wewnątrz ViewModel — prowadzi to do wycieków pamięci, ponieważ ViewModel żyje dłużej niż Activity z UI. Zamiast tego używaj LiveData, StateFlow lub SavedStateHandle do przekazywania danych między ViewModel a UI.

ViewModel w architekturze MVVM

We wzorcu MVVM (Model-View-ViewModel) ViewModel zajmuje centralne miejsce między View (Activity/Fragment) a Model (repozytorium, baza danych, API). View subskrybuje reaktywne dane ViewModel (LiveData, StateFlow) i automatycznie aktualizuje się przy ich zmianie. ViewModel nie wie o istnieniu View — dostarcza tylko dane i polecenia, a View decyduje, jak je wyświetlić.

Porównanie MVP i MVVM: w MVP Presenter bezpośrednio wywołuje metody View (interfejsu), tworząc ścisłe powiązanie. W MVVM ViewModel publikuje reaktywne strumienie danych, a View subskrybuje je — komunikacja jest jednostronna i testowalna. Według ankiety JetBrains Developer Survey (2024), 68% programistów Android używa MVVM jako głównej architektury, a ViewModel jest kluczowym komponentem tego wzorca.

W IT Sectr stosujemy MVVM z ViewModel od 2018 roku we wszystkich komercyjnych projektach w Kotlin. Praktyka pokazuje, że takie podejście skraca czas debugowania logiki UI o 30–40% dzięki wyraźnemu podziałowi odpowiedzialności i testowalności logiki biznesowej bez emulatora.

ViewModelProvider i fabryki: tworzenie z parametrami

ViewModelProvider — standardowy sposób uzyskiwania ViewModel we fragmencie lub Activity. Domyślnie ViewModelProvider tworzy ViewModel przez pusty konstruktor (bez argumentów). Jeśli ViewModel wymaga parametrów (np. repozytorium lub kontekstu aplikacji), należy zaimplementować 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
    }
}

Fabryka jest przekazywana do ViewModelProvider przy uzyskiwaniu ViewModel z Fragment lub Activity. SavedStateHandle — alternatywny mechanizm przekazywania parametrów, który pojawił się w AndroidX 1.2.0: ViewModel automatycznie otrzymuje SavedStateHandle przez konstruktor, a argumenty są przekazywane przez Bundle bez pisania własnej fabryki.

viewModelScope i korutyny w ViewModel

viewModelScope — to CoroutineScope wbudowany w ViewModel i powiązany z jego cyklem życia. Wszystkie korutyny uruchomione w viewModelScope są automatycznie anulowane przy wywołaniu onCleared(), co zapobiega wyciekom pamięci i operacjom w tle po zniszczeniu 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()
        // Wszystkie korutyny viewModelScope są automatycznie anulowane
    }
}

Korutyny w viewModelScope domyślnie wykonują się na Dispatchers.Main. Dla operacji sieciowych lub dyskowych przełączaj się na Dispatchers.IO za pomocą withContext lub określaj dyspozytora w launch. Według Google (Android Dev Summit 2024), użycie viewModelScope zmniejsza wycieki pamięci związane z korutynami o 95% w porównaniu z ręcznym zarządzaniem Job.

ViewModel z Hilt i Koin: podejścia DI

Hilt — oficjalna biblioteka dependency injection od Google dla Android, zbudowana na Dagger. Z Hilt nie trzeba ręcznie pisać ViewModelProvider.Factory — wystarczy adnotować konstruktor ViewModel adnotacją @HiltViewModel. Hilt automatycznie tworzy fabrykę i wstrzykuje zależności zadeklarowane w konstruktorze.

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")
        }
    }
}

// We Fragment — bez fabryki:
val viewModel: ProfileViewModel = by viewModels()

Koin — alternatywna biblioteka DI bez generowania kodu. W Koin ViewModel jest deklarowany w module przez viewModel { }, a we fragmencie uzyskiwany przez by viewModel(). Wybór między Hilt a Koin zależy od projektu: Hilt zapewnia sprawdzanie grafu zależności na etapie kompilacji, Koin — jest lżejszy i nie wymaga kapt/ksp. W IT Sectr używamy Hilt w dużych projektach (ponad 50 ekranów) i Koin w średnich.

Przykłady kodu: ViewModel w Kotlin

Przykład 1: Podstawowa ViewModel z licznikiem

Najprostsza ViewModel przechowująca licznik całkowity, który nie resetuje się przy obrocie ekranu. Demonstruje podstawowy wzorzec użycia MutableLiveData i 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
    }
}

Przykład 2: ViewModel z SavedStateHandle

ViewModel używająca SavedStateHandle do automatycznego zapisywania stanu nawet przy zniszczeniu procesu przez system. SavedStateHandle to jedyny mechanizm zachowujący dane przy minimalizowaniu aplikacji w tle i jej zakończeniu.

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 z SavedStateHandle automatycznie zapisuje ostatnią wartość w Bundle. Przy odtworzeniu procesu (np. po minimalizacji i zabiciu aplikacji) Bundle jest odtwarzany, a LiveData otrzymuje poprzednią wartość. Według testów Google, SavedStateHandle gwarantuje zachowanie do 5 KB danych w Bundle — wystarczająco dla pól tekstowych, ID i serializowanych obiektów JSON.

Często zadawane pytania

Czym ViewModel różni się od onSaveInstanceState?

ViewModel przechowuje dane w pamięci RAM procesu — są dostępne natychmiast bez serializacji, nadają się do złożonych obiektów (listy, Bitmap, odpowiedzi sieciowe). onSaveInstanceState() serializuje dane do Bundle (maksymalnie 1 MB na transakcję od Android 12) i nadaje się tylko do prostych prymitywów, String i Serializable/Parcelable. ViewModel + SavedStateHandle — zalecana przez Google kombinacja: ViewModel dla danych runtime, SavedStateHandle do odtwarzania przy zabiciu procesu.

Czy trzeba ręcznie czyścić ViewModel?

Nie, system automatycznie wywołuje onCleared() przy zakończeniu scope. Ręczne czyszczenie przez viewModelStore.clear() jest wymagane tylko w testach, aby zapobiec wyciekom między przypadkami testowymi. W kodzie produkcyjnym nigdy nie wywołuj clear() ręcznie — to narusza cykl życia ViewModel i może prowadzić do nieprzewidywalnego zachowania UI.

Czy można używać ViewModel w Compose?

Tak, ViewModel jest w pełni obsługiwany w Jetpack Compose przez funkcję viewModel(). W Compose ViewModel jest uzyskiwany na poziomie scope Composable i automatycznie czyszczony przy wyjściu z scope. Compose-wersja MVVM nazywa się Unidirectional Data Flow (UDF): ViewModel publikuje StateFlow, a funkcje Composable subskrybują przez collectAsState(). Compose-wariant podejścia reducer — MVI z ViewModel.

Czego nie wolno przechowywać w ViewModel?

Zabronione jest przechowywanie referencji do Activity, Fragment, View, Context (z wyjątkiem Application). Prowadzi to do wycieków pamięci, ponieważ ViewModel żyje dłużej niż kontekst UI. Nie przechowuj serializowanych stanów View (np. pozycji RecyclerView) — używaj LayoutManager.onSaveInstanceState(). Unikaj przechowywania dużych ilości danych (ponad 10 MB) — przy minimalizacji procesu dane zostaną utracone bez SavedStateHandle.

Jak testować ViewModel?

ViewModel testuje się jak zwykłą klasę Kotlin bez emulatora: tworzysz instancję, wywołujesz metody, sprawdzasz stan LiveData lub StateFlow. Do testowania korutyn używaj runTest z kotlinx-coroutines-test z TestDispatcher. Dla ViewModel z Hilt używaj @HiltViewModelTest i hiltViewModel() we fragmencie testowym. Według Google, testy jednostkowe pokrywają 80–90% logiki ViewModel bez testów instrumentalnych.

Podsumowanie

  • ViewModel — komponent Jetpack do przechowywania danych UI, przetrzymujący zmiany konfiguracji bez utraty stanu.
  • Cykl życia ViewModel jest powiązany ze scope (Activity/Fragment), a nie z instancją Activity — czyszczenie następuje przy zakończeniu scope.
  • ViewModelProvider — metoda fabryczna do tworzenia ViewModel; dla parametrów implementuje się ViewModelProvider.Factory.
  • viewModelScope — wbudowany CoroutineScope, automatycznie anulujący korutyny przy onCleared(), co eliminuje wycieki pamięci.
  • SavedStateHandle — mechanizm zapisywania stanu przy zabiciu procesu, integrowany w konstruktorze ViewModel.
  • Hilt i @HiltViewModel — standardowy sposób DI dla ViewModel w dużych projektach; Koin — lekka alternatywa bez generowania kodu.
  • ViewModel — podstawa architektur MVVM i UDF, używana w 82% aplikacji Jetpack według Google I/O 2025.

Opracujemy aplikację mobilną pod klucz

IT Sectr tworzy aplikacje na iOS i Androida dla startupów i firm od 2017 roku. Doradzimy Ci i zaproponujemy najlepsze rozwiązanie.

Omów projekt

Przeczytaj również