ViewModel è un componente Android Jetpack Architecture progettato per archiviare e gestire i dati UI tenendo conto del ciclo di vita di Activity e Fragment. Secondo Google I/O 2025, ViewModel è utilizzato nel 82% delle applicazioni Android moderne costruite su Jetpack. A differenza delle classi normali, ViewModel sopravvive automaticamente alla rotazione dello schermo e ad altri cambiamenti di configurazione, preservando lo stato dell'UI senza perdita di dati. L'architettura MVVM (Model-View-ViewModel) si basa su ViewModel come livello centrale che collega la logica di business con l'interfaccia.
Punti chiave
ViewModel è una classe della libreria Android Jetpack progettata per archiviare e gestire i dati relativi all'interfaccia utente, tenendo conto del ciclo di vita di un'Activity o Fragment. Il compito principale di ViewModel è separare la logica di preparazione dei dati dal livello UI e preservare questi dati durante i cambiamenti di configurazione come rotazione dello schermo, cambio tema o lingua.
Prima dell'avvento di ViewModel, gli sviluppatori memorizzavano lo stato dell'UI direttamente nell'Activity o Fragment. Quando lo schermo ruota, Android distrugge l'Activity e ne crea una nuova — tutti i dati non salvati andavano persi. La soluzione era salvare lo stato tramite onSaveInstanceState() o usare onRetainNonConfigurationInstance(), ma entrambi gli approcci richiedevano gestione manuale, serializzazione e non erano adatti a oggetti complessi. ViewModel risolve questo problema a livello di framework: i dati vivono in memoria separatamente dall'UI e vengono automaticamente restituiti quando l'Activity viene ricreata.
Secondo la documentazione di Android Developers (2025), ViewModel memorizza i dati nella RAM del processo — è 10–50 volte più veloce del ripristino da Bundle tramite onSaveInstanceState(), che richiede serializzazione in un array di byte. ViewModel è raccomandato per tutti gli schermi dove i dati sono più complessi di un primitivo semplice o stringa.
Il ciclo di vita di ViewModel differisce fondamentalmente dal ciclo di vita di Activity: ViewModel non viene distrutto durante la rotazione dello schermo e vive fino al completamento dello scope (Activity.finish() o Fragment rimosso). Ciò significa che qualsiasi dato caricato in ViewModel rimane disponibile durante i cambiamenti di configurazione senza dover ricaricare dalla rete o dal database.
Al momento della creazione dell'Activity, il sistema alloca ViewModel tramite ViewModelProvider. Alla prima chiamata di ViewModelProvider.get(ViewModel::class.java) viene creata una nuova istanza di ViewModel. Alle chiamate successive (anche dopo rotazione) viene restituita la stessa istanza. La pulizia di ViewModel avviene automaticamente quando viene chiamato onCleared() — questo metodo viene invocato quando l'Activity termina (finish()) o il Fragment viene completamente rimosso. Lo sviluppatore può sovrascrivere onCleared() per liberare risorse: annullare l'iscrizione a Flow, cancellare coroutine, chiudere socket.
Google nella documentazione Jetpack sottolinea: non conservare mai un riferimento ad Activity o View dentro ViewModel — questo causa perdite di memoria perché ViewModel sopravvive all'Activity con la sua UI. Invece, usa LiveData, StateFlow o SavedStateHandle per passare dati tra ViewModel e UI.
Nel pattern MVVM (Model-View-ViewModel), ViewModel occupa una posizione centrale tra View (Activity/Fragment) e Model (repository, DB, API). La View si sottoscrive ai dati reattivi di ViewModel (LiveData, StateFlow) e si aggiorna automaticamente quando cambiano. ViewModel non conosce l'esistenza della View — fornisce solo dati e comandi, e la View decide come visualizzarli.
Confronto tra MVP e MVVM: in MVP, il Presenter chiama direttamente i metodi della View (interfaccia), creando un forte accoppiamento. In MVVM, ViewModel pubblica flussi di dati reattivi e la View vi si sottoscrive — la connessione è unidirezionale e testabile. Secondo il JetBrains Developer Survey (2024), il 68% degli sviluppatori Android usa MVVM come architettura principale, e ViewModel è un componente chiave di questo pattern.
In IT Sectr, utilizziamo MVVM con ViewModel dal 2018 in tutti i progetti commerciali in Kotlin. La pratica mostra che questo approccio riduce i tempi di debug della logica UI del 30–40% grazie alla chiara separazione delle responsabilità e alla testabilità della logica di business senza emulatore.
ViewModelProvider è il modo standard per ottenere ViewModel in un fragment o Activity. Per impostazione predefinita, ViewModelProvider crea ViewModel tramite un costruttore vuoto (senza argomenti). Se ViewModel richiede parametri (ad esempio, un repository o contesto applicativo), è necessario implementare 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
}
}
La factory viene passata a ViewModelProvider quando si ottiene ViewModel da Fragment o Activity. SavedStateHandle è un meccanismo alternativo di passaggio parametri introdotto in AndroidX 1.2.0: ViewModel riceve automaticamente SavedStateHandle tramite il costruttore e gli argomenti vengono passati tramite Bundle senza scrivere una factory personalizzata.
viewModelScope è un CoroutineScope integrato in ViewModel e legato al suo ciclo di vita. Tutte le coroutine lanciate in viewModelScope vengono automaticamente cancellate quando viene chiamato onCleared(), prevenendo perdite di memoria e operazioni in background dopo la distruzione di 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()
// Tutte le coroutine di viewModelScope vengono cancellate automaticamente
}
}
Le coroutine in viewModelScope vengono eseguite su Dispatchers.Main per impostazione predefinita. Per operazioni di rete o disco, passa a Dispatchers.IO usando withContext o specifica il dispatcher in launch. Secondo Google (Android Dev Summit 2024), l'uso di viewModelScope riduce le perdite di memoria correlate alle coroutine del 95% rispetto alla gestione manuale di Job.
Hilt è la libreria ufficiale di dependency injection di Google per Android, costruita su Dagger. Con Hilt non è necessario scrivere ViewModelProvider.Factory manualmente — basta annotare il costruttore di ViewModel con @HiltViewModel. Hilt crea automaticamente la factory e inietta le dipendenze dichiarate nel costruttore.
@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 — senza factory:
val viewModel: ProfileViewModel = by viewModels()
Koin è una libreria DI alternativa senza generazione di codice. In Koin, ViewModel viene dichiarato in un modulo tramite viewModel { }, e nel fragment viene ottenuto tramite by viewModel(). La scelta tra Hilt e Koin dipende dal progetto: Hilt fornisce la verifica del grafo delle dipendenze in fase di compilazione, Koin è più leggero e non richiede kapt/ksp. In IT Sectr, utilizziamo Hilt in progetti grandi (più di 50 schermate) e Koin in progetti medi.
Un semplice ViewModel che memorizza un contatore intero che non viene resettato durante la rotazione dello schermo. Mostra il pattern base di utilizzo di MutableLiveData e 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 che utilizza SavedStateHandle per preservare automaticamente lo stato anche quando il processo viene terminato dal sistema. SavedStateHandle è l'unico meccanismo che salva i dati quando l'app viene minimizzata in background e terminata.
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 da SavedStateHandle salva automaticamente l'ultimo valore in Bundle. Quando il processo viene ricreato (ad esempio, dopo aver minimizzato e chiuso l'app), il Bundle viene ripristinato e LiveData riceve il valore precedente. Secondo i test di Google, SavedStateHandle garantisce il salvataggio fino a 5 KB di dati in Bundle — sufficiente per campi di testo, ID e oggetti JSON serializzati.
Domande frequenti
ViewModel memorizza i dati nella RAM del processo — sono disponibili immediatamente senza serializzazione, adatti per oggetti complessi (liste, Bitmap, risposte di rete). onSaveInstanceState() serializza i dati in Bundle (massimo 1 MB per transazione a partire da Android 12) ed è adatto solo per primitivi semplici, String e Serializable/Parcelable. ViewModel + SavedStateHandle è la combinazione raccomandata da Google: ViewModel per i dati runtime, SavedStateHandle per il ripristino quando il processo viene terminato.
No, il sistema chiama automaticamente onCleared() al termine dello scope. La pulizia manuale tramite viewModelStore.clear() è necessaria solo nei test per prevenire perdite tra casi di test. Nel codice di produzione, non chiamare mai clear() manualmente — questo rompe il ciclo di vita di ViewModel e può portare a un comportamento imprevedibile dell'UI.
Sì, ViewModel è completamente supportato in Jetpack Compose tramite la funzione viewModel(). In Compose, ViewModel viene ottenuto a livello di scope Composable e viene automaticamente pulito all'uscita dallo scope. La versione Compose di MVVM si chiama Flusso di Dati Unidirezionale (UDF): ViewModel pubblica StateFlow e le funzioni Composable si sottoscrivono tramite collectAsState(). La variante Compose dell'approccio reducer è MVI con ViewModel.
È vietato conservare riferimenti ad Activity, Fragment, View o Context (tranne Application). Questo causa perdite di memoria perché ViewModel sopravvive al contesto UI. Non conservare stati di View serializzati (ad esempio, posizione di RecyclerView) — usa LayoutManager.onSaveInstanceState(). Evita di conservare grandi quantità di dati (più di 10 MB) — quando il processo viene minimizzato, i dati andranno persi senza SavedStateHandle.
ViewModel viene testato come una normale classe Kotlin senza emulatore: crea un'istanza, chiama metodi, verifica lo stato di LiveData o StateFlow. Per testare le coroutine, usa runTest da kotlinx-coroutines-test con TestDispatcher. Per ViewModel con Hilt, usa @HiltViewModelTest e hiltViewModel() in un fragment di test. Secondo Google, i test unitari coprono 80–90% della logica di ViewModel senza test strumentati.
Riepilogo
Svilupperemo un'applicazione mobile chiavi in mano
IT Sectr crea applicazioni iOS e Android per startup e aziende dal 2017. Ti consulteremo e ti proporremo la soluzione migliore.
Leggi anche