ViewModel — een Android Jetpack Architecture-component voor het opslaan en beheren van UI-gegevens rekening houdend met de levenscyclus van Activity en Fragment. Volgens Google I/O 2025 wordt ViewModel gebruikt in 82% van de moderne Android-apps gebouwd op Jetpack. In tegenstelling tot gewone klassen overleeft ViewModel automatisch schermrotatie en andere configuratiewijzigingen, behoudend de UI-status zonder gegevensverlies. De MVVM-architectuur (Model-View-ViewModel) vertrouwt op ViewModel als centrale laag die bedrijfslogica met de interface verbindt.
Belangrijkste punten
ViewModel — een klasse uit de Android Jetpack-bibliotheek voor het opslaan en beheren van gegevens die aan de gebruikersinterface zijn gekoppeld, rekening houdend met de levenscyclus van Activity of Fragment. De belangrijkste taak van ViewModel is het scheiden van de gegevensvoorbereidingslogica van de UI-laag en het behouden van deze gegevens bij configuratiewijzigingen zoals schermrotatie, themawijziging of lokalisatie.
Vóór de komst van ViewModel slaan ontwikkelaars de UI-status rechtstreeks op in de Activity of Fragment. Bij schermrotatie vernietigt Android de Activity en maakt een nieuwe aan — alle niet-opgeslagen gegevens gingen verloren. De oplossing was statusopslag via onSaveInstanceState() of het gebruik van onRetainNonConfigurationInstance(), maar beide benaderingen vereisten handmatig beheer, serialisatie en waren niet geschikt voor complexe objecten. ViewModel lost dit probleem op framework-niveau op: gegevens leven in het geheugen gescheiden van de UI en worden automatisch teruggegeven bij het opnieuw aanmaken van de Activity.
Volgens de Android Developers-documentatie (2025) slaat ViewModel gegevens op in het RAM van het proces — dit is 10–50 keer sneller dan herstel uit Bundle via onSaveInstanceState(), waar serialisatie naar een byte-array nodig is. ViewModel wordt aanbevolen voor alle schermen waar gegevens complexer zijn dan een eenvoudig primitief of string.
De levenscyclus van ViewModel verschilt fundamenteel van de levenscyclus van Activity: ViewModel wordt niet vernietigd bij schermrotatie en leeft tot de volledige voltooiing van de scope (Activity.finish() of Fragment removed). Dit betekent dat alle gegevens die in ViewModel zijn geladen, beschikbaar blijven bij configuratiewijzigingen zonder opnieuw laden uit het netwerk of de database.
Op het moment van het aanmaken van de Activity wijst het systeem ViewModel toe via ViewModelProvider. Bij de eerste aanroep van ViewModelProvider.get(ViewModel::class.java) wordt een nieuwe ViewModel-instantie gemaakt. Bij volgende aanroepen (ook na rotatie) wordt dezelfde instantie teruggegeven. Het opschonen van ViewModel gebeurt automatisch bij het aanroepen van onCleared() — deze methode wordt aangeroepen wanneer de Activity wordt beëindigd (finish()) of Fragment volledig wordt verwijderd. De ontwikkelaar kan onCleared() overschrijven om bronnen vrij te geven: uitschrijven van Flow, annuleren van coroutines, sluiten van sockets.
Google benadrukt in de Jetpack-documentatie: sla nooit een verwijzing naar Activity of View op in ViewModel — dit leidt tot geheugenlekken omdat ViewModel langer leeft dan de Activity met UI. Gebruik in plaats daarvan LiveData, StateFlow of SavedStateHandle voor gegevensoverdracht tussen ViewModel en UI.
In het MVVM (Model-View-ViewModel)-patroon neemt ViewModel de centrale plaats in tussen View (Activity/Fragment) en Model (repository, database, API). View abonneert zich op de reactieve gegevens van ViewModel (LiveData, StateFlow) en wordt automatisch bijgewerkt wanneer deze veranderen. ViewModel weet niet van het bestaan van View — het levert alleen gegevens en opdrachten, en View beslist hoe deze worden weergegeven.
Vergelijking van MVP en MVVM: in MVP roept Presenter rechtstreeks methoden van View (interface) aan, waardoor een strakke koppeling ontstaat. In MVVM publiceert ViewModel reactieve gegevensstromen en View abonneert zich erop — communicatie is eenrichtingsverkeer en testbaar. Volgens de JetBrains Developer Survey (2024) gebruikt 68% van de Android-ontwikkelaars MVVM als belangrijkste architectuur, en ViewModel is de sleutelcomponent van dit patroon.
Bij IT Sectr passen we MVVM met ViewModel sinds 2018 toe in alle commerciële projecten op Kotlin. De praktijk leert dat deze aanpak de tijd voor het debuggen van UI-logica met 30–40% verkort dankzij duidelijke scheiding van verantwoordelijkheden en testbaarheid van bedrijfslogica zonder emulator.
ViewModelProvider — de standaard manier om een ViewModel te verkrijgen in een fragment of Activity. Standaard maakt ViewModelProvider een ViewModel via een lege constructor (zonder argumenten). Als ViewModel parameters nodig heeft (bijvoorbeeld een repository of applicatiecontext), moet ViewModelProvider.Factory worden geïmplementeerd.
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
}
}
De fabriek wordt doorgegeven aan ViewModelProvider bij het verkrijgen van de ViewModel uit Fragment of Activity. SavedStateHandle — een alternatief mechanisme voor parameteroverdracht, verschenen in AndroidX 1.2.0: ViewModel ontvangt automatisch SavedStateHandle via de constructor en argumenten worden via Bundle doorgegeven zonder het schrijven van een eigen fabriek.
viewModelScope — een CoroutineScope ingebouwd in ViewModel en gekoppeld aan zijn levenscyclus. Alle coroutines die in viewModelScope worden gestart, worden automatisch geannuleerd bij het aanroepen van onCleared(), wat geheugenlekken en achtergrondbewerkingen na vernietiging van ViewModel voorkomt.
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()
// Alle coroutines van viewModelScope worden automatisch geannuleerd
}
}
Coroutines in viewModelScope worden standaard uitgevoerd op Dispatchers.Main. Schakel voor netwerk- of schijfbewerkingen over naar Dispatchers.IO met withContext of geef een dispatcher op in launch. Volgens Google (Android Dev Summit 2024) vermindert het gebruik van viewModelScope geheugenlekken gerelateerd aan coroutines met 95% in vergelijking met handmatig Job-beheer.
Hilt — de officiële dependency injection-bibliotheek van Google voor Android, gebouwd op Dagger. Met Hilt hoeft u ViewModelProvider.Factory niet handmatig te schrijven — het is voldoende om de ViewModel-constructor te annoteren met @HiltViewModel. Hilt maakt automatisch de fabriek aan en injecteert de in de constructor gedeclareerde afhankelijkheden.
@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")
}
}
}
// In Fragment — zonder fabriek:
val viewModel: ProfileViewModel = by viewModels()
Koin — een alternatieve DI-bibliotheek zonder codegeneratie. In Koin wordt ViewModel gedeclareerd in de module via viewModel { } en in fragment verkregen via by viewModel(). De keuze tussen Hilt en Koin hangt af van het project: Hilt biedt controle van de afhankelijkheidsgraaf tijdens compilatie, Koin is lichter en vereist geen kapt/ksp. Bij IT Sectr gebruiken we Hilt in grote projecten (meer dan 50 schermen) en Koin in middelgrote projecten.
De eenvoudigste ViewModel die een integer-teller opslaat die niet wordt gereset bij schermrotatie. Demonstreert het basispatroon van het gebruik van MutableLiveData en 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 die SavedStateHandle gebruikt voor automatische statusopslag, zelfs bij vernietiging van het proces door het systeem. SavedStateHandle is het enige mechanisme dat gegevens behoudt bij het minimaliseren van de app naar de achtergrond en het beëindigen ervan.
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 uit SavedStateHandle slaat automatisch de laatste waarde op in Bundle. Bij het opnieuw aanmaken van het proces (bijvoorbeeld na minimaliseren en doden van de app) wordt Bundle hersteld en ontvangt LiveData de vorige waarde. Volgens Google-tests garandeert SavedStateHandle het behoud van tot 5 KB aan gegevens in Bundle — voldoende voor tekstvelden, ID's en geserialiseerde JSON-objecten.
Veelgestelde vragen
ViewModel slaat gegevens op in het RAM van het proces — ze zijn direct beschikbaar zonder serialisatie, geschikt voor complexe objecten (lijsten, Bitmap, netwerkantwoorden). onSaveInstanceState() serialiseert gegevens naar Bundle (maximaal 1 MB per transactie vanaf Android 12) en is alleen geschikt voor eenvoudige primitieven, String en Serializable/Parcelable. ViewModel + SavedStateHandle — de door Google aanbevolen combinatie: ViewModel voor runtime-gegevens, SavedStateHandle voor herstel bij het doden van het proces.
Nee, het systeem roept automatisch onCleared() aan bij voltooiing van de scope. Handmatig opschonen via viewModelStore.clear() is alleen nodig in tests om lekken tussen testgevallen te voorkomen. In productiecode nooit clear() handmatig aanroepen — dit schendt de levenscyclus van ViewModel en kan leiden tot onvoorspelbaar UI-gedrag.
Ja, ViewModel wordt volledig ondersteund in Jetpack Compose via de functie viewModel(). In Compose wordt ViewModel verkregen op het niveau van de Composable-scope en automatisch opgeschoond bij het verlaten van de scope. De Compose-versie van MVVM heet Unidirectional Data Flow (UDF): ViewModel publiceert StateFlow en Composable-functies abonneren zich via collectAsState(). De Compose-variant van de reducer-benadering — MVI met ViewModel.
Het is verboden verwijzingen naar Activity, Fragment, View, Context (behalve Application) op te slaan. Dit leidt tot geheugenlekken omdat ViewModel langer leeft dan de UI-context. Bewaar geen geserialiseerde View-toestanden (bijvoorbeeld RecyclerView-positie) — gebruik LayoutManager.onSaveInstanceState(). Vermijd het opslaan van grote hoeveelheden gegevens (meer dan 10 MB) — bij minimaliseren van het proces gaan gegevens verloren zonder SavedStateHandle.
ViewModel wordt getest als een gewone Kotlin-klasse zonder emulator: maak een instantie, roep methoden aan, controleer de status van LiveData of StateFlow. Gebruik voor het testen van coroutines runTest uit kotlinx-coroutines-test met TestDispatcher. Gebruik voor ViewModel met Hilt @HiltViewModelTest en hiltViewModel() in het testfragment. Volgens Google dekken unittesten 80–90% van de ViewModel-logica zonder instrumentele tests.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook