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() است که نیاز به سریالسازی به آرایه بایت دارد. 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، لغو کوروتینها، بستن سوکتها.
Google در مستندات Jetpack تأکید میکند: هرگز مرجعی به Activity یا View در داخل ViewModel ذخیره نکنید — این منجر به نشت حافظه میشود، زیرا ViewModel بیشتر از Activity با UI عمر میکند. به جای آن از LiveData، StateFlow یا SavedStateHandle برای انتقال داده بین ViewModel و UI استفاده کنید.
در الگوی MVVM (Model-View-ViewModel)، ViewModel جایگاه مرکزی بین View (Activity/Fragment) و Model (repository، پایگاه داده، 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، ما از سال 2018 در تمام پروژههای تجاری خود بر Kotlin از MVVM با ViewModel استفاده میکنیم. تجربه نشان میدهد که این رویکرد زمان اشکالزدایی منطق UI را به دلیل تفکیک واضح مسئولیتها و قابلیت تست منطق کسبوکار بدون شبیهساز 30–40% کاهش میدهد.
ViewModelProvider — روش استاندارد دریافت ViewModel در Fragment یا Activity. به طور پیشفرض، ViewModelProvider ViewModel را از طریق سازنده خالی (بدون آرگومان) ایجاد میکند. اگر ViewModel نیاز به پارامتر داشته باشد (مثلاً repository یا context برنامه)، باید 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
}
}
کارخانه هنگام دریافت ViewModel از Fragment یا Activity به ViewModelProvider منتقل میشود. SavedStateHandle — مکانیزم جایگزین انتقال پارامتر که در AndroidX 1.2.0 ظاهر شد: ViewModel به طور خودکار SavedStateHandle را از طریق سازنده دریافت میکند و آرگومانها از طریق Bundle بدون نوشتن کارخانه اختصاصی منتقل میشوند.
viewModelScope — CoroutineScope تعبیهشده در ViewModel که به چرخه حیات آن وابسته است. تمام کوروتینهای راهاندازی شده در 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()
// همه کوروتینهای viewModelScope به طور خودکار لغو شدند
}
}
کوروتینها در viewModelScope به طور پیشفرض روی Dispatchers.Main اجرا میشوند. برای عملیات شبکه یا دیسک با withContext به Dispatchers.IO سوئیچ کنید یا dispatcher را در launch مشخص کنید. طبق Google (Android Dev Summit 2024)، استفاده از viewModelScope نشت حافظه مرتبط با کوروتینها را در مقایسه با مدیریت دستی Job 95% کاهش میدهد.
Hilt — کتابخانه رسمی dependency injection از Google برای Android، ساخته شده بر Dagger. با Hilt نیازی به نوشتن دستی ViewModelProvider.Factory نیست — کافی است سازنده ViewModel را با annotation @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 کیلوبایت داده در Bundle را تضمین میکند — برای فیلدهای متنی، IDها و اشیاء JSON سریالشده کافی است.
سؤالات متداول
ViewModel دادهها را در RAM پردازش ذخیره میکند — آنها بدون سریالسازی فوراً در دسترس هستند و برای اشیاء پیچیده (لیستها، Bitmap، پاسخهای شبکه) مناسبند. onSaveInstanceState() دادهها را در Bundle سریالسازی میکند (حداکثر 1 مگابایت در هر تراکنش از Android 12) و فقط برای primitiveهای ساده، String و Serializable/Parcelable مناسب است. ViewModel + SavedStateHandle — ترکیب توصیهشده Google: ViewModel برای دادههای runtime، SavedStateHandle برای بازیابی هنگام کشته شدن فرآیند.
خیر، سیستم به طور خودکار هنگام اتمام scope onCleared() را فراخوانی میکند. پاکسازی دستی از طریق viewModelStore.clear() فقط در تستها برای جلوگیری از نشت بین موارد تستی لازم است. در کد production هرگز clear() را دستی فراخوانی نکنید — این چرخه حیات ViewModel را نقض کرده و میتواند منجر به رفتار غیرقابل پیشبینی UI شود.
بله، ViewModel در Jetpack Compose از طریق تابع viewModel() کاملاً پشتیبانی میشود. در Compose، ViewModel در سطح scope Composable دریافت میشود و هنگام خروج از scope به طور خودکار پاک میشود. نسخه Compose از MVVM Unidirectional Data Flow (UDF) نام دارد: ViewModel StateFlow منتشر میکند و توابع Composable از طریق collectAsState() اشتراک میکنند. variant رویکرد reducer در Compose — MVI با ViewModel.
ذخیره مراجع به Activity، Fragment، View، Context (به جز Application) ممنوع است. این منجر به نشت حافظه میشود، زیرا ViewModel بیشتر از بافت UI عمر میکند. ذخیره نکنید وضعیتهای سریالشده View (مثلاً موقعیت RecyclerView) — از LayoutManager.onSaveInstanceState() استفاده کنید. اجتناب کنید از ذخیره حجم زیادی داده (بیش از 10 مگابایت) — هنگام کوچکسازی فرآیند دادهها بدون SavedStateHandle از بین میروند.
ViewModel مانند یک کلاس معمولی Kotlin بدون شبیهساز تست میشود: نمونه ایجاد کنید، متدها را فراخوانی کنید، وضعیت LiveData یا StateFlow را بررسی کنید. برای تست کوروتینها از runTest از kotlinx-coroutines-test با TestDispatcher استفاده کنید. برای ViewModel با Hilt از @HiltViewModelTest و hiltViewModel() در Fragment تستی استفاده کنید. طبق Google، تستهای واحد 80–90% منطق ViewModel را بدون تستهای ابزاری پوشش میدهند.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید