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(), όπου απαιτείται σειριοποίηση σε πίνακα byte. Το ViewModel συνιστάται για όλες τις οθόνες όπου τα δεδομένα είναι πιο σύνθετα από ένα απλό primitive ή συμβολοσειρά.
Ο κύκλος ζωής του 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, ακύρωση coroutines, κλείσιμο sockets.
Η 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 στο fragment ή 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 και δεμένο με τον κύκλο ζωής του. Όλα τα coroutines που ξεκινούν στο 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()
// Όλα τα coroutines του viewModelScope ακυρώνονται αυτόματα
}
}
Τα coroutines στο viewModelScope εκτελούνται από προεπιλογή στο Dispatchers.Main. Για λειτουργίες δικτύου ή δίσκου, μεταβείτε στο Dispatchers.IO με withContext ή καθορίστε dispatcher στο launch. Σύμφωνα με την Google (Android Dev Summit 2024), η χρήση viewModelScope μειώνει τις διαρροές μνήμης που σχετίζονται με coroutines κατά 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 { } και στο fragment λαμβάνεται μέσω 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) και είναι κατάλληλο μόνο για απλά primitives, String και Serializable/Parcelable. ViewModel + SavedStateHandle — συνδυασμός που προτείνει η Google: ViewModel για δεδομένα χρόνου εκτέλεσης, SavedStateHandle για επαναφορά κατά τον τερματισμό διεργασίας.
Όχι, το σύστημα καλεί αυτόματα onCleared() κατά την ολοκλήρωση του scope. Χειροκίνητος καθαρισμός μέσω viewModelStore.clear() απαιτείται μόνο σε δοκιμές για αποτροπή διαρροών μεταξύ περιπτώσεων δοκιμής. Σε κώδικα παραγωγής μην καλείτε ποτέ το 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. Για δοκιμή coroutines, χρησιμοποιήστε runTest από kotlinx-coroutines-test με TestDispatcher. Για ViewModel με Hilt, χρησιμοποιήστε @HiltViewModelTest και hiltViewModel() στο fragment δοκιμής. Σύμφωνα με την Google, οι δοκιμές μονάδας καλύπτουν 80–90% της λογικής ViewModel χωρίς ενόργανες δοκιμές.
Σύνοψη
Θα αναπτύξουμε μια εφαρμογή για κινητά έτοιμη για χρήση
Η IT Sectr δημιουργεί εφαρμογές iOS και Android για νεοφυείς επιχειρήσεις και επιχειρήσεις από το 2017. Θα σας συμβουλεύσουμε και θα προτείνουμε την καλύτερη λύση.
Διαβάστε επίσης