viewModelScope: چیست، اتصال به ViewModel و کار در Android

نویسنده: IT Sectr منتشر شده: 2026-06-23 زمان مطالعه: 9 دقیقه

viewModelScope — یک CoroutineScope داخلی از کتابخانه androidx.lifecycle است که به چرخه حیات ViewModel متصل شده و هنگام پاک‌سازی آن به طور خودکار لغو می‌شود. به گفته Google Android Developers, 2025، viewModelScope مکانیزم استانداردی برای راه‌اندازی کوروتین‌ها در معماری MVVM است و کار ایمن با عملیات‌های ناهمگام را بدون خطر نشت حافظه فراهم می‌کند. ViewModelScope به طور پیش‌فرض از Dispatchers.Main استفاده می‌کند و تمام عملیات‌های IO درون آن باید از طریق withContext انجام شوند.

نکات اصلی

  • viewModelScope — CoroutineScope از lifecycle-viewmodel-ktx که در onCleared() ViewModel لغو می‌شود
  • Dispatchers.Main —توزیع‌کننده پیش‌فرض، بنابراین به‌روزرسانی‌های UI درون کوروتین‌ها ایمن هستند
  • onCleared — فراخوانی که هنگام فراخوانی آن viewModelScope تمام کوروتین‌های فعال را به طور خودکار لغو می‌کند
  • clear() در مقابل onCleared() — clear() توسط فریم‌ورک قبل از onCleared فراخوانی می‌شود که لغو scope را تضمین می‌کند
  • launch — روش اصلی راه‌اندازی کوروتین‌ها در viewModelScope برای عملیات‌های fire-and-forget

viewModelScope در Android چیست؟

viewModelScope — یک ویژگی الحاقی (extension property) بر روی رابط ViewModel است که در کتابخانه lifecycle-viewmodel-ktx (از نسخه 2.1.0) اضافه شده است. این ویژگی یک CoroutineScope آماده و متصل به چرخه حیات ViewModel فراهم می‌کند.

kotlin
// ساختار داخلی (ساده‌شده)
val ViewModel.viewModelScope: CoroutineScope
    get() {
        val scope = this.getTag(JOB_KEY)
        if (scope != null) return scope
        return CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
            .also { setTag(JOB_KEY, it) }
    }

Scope به صورت تنبل (lazy) در اولین دسترسی ایجاد می‌شود و از طریق setTag ذخیره می‌شود. از SupervisorJob استفاده می‌شود، به این معنی که استثنا در یک کوروتین فرزند، بقیه را لغو نمی‌کند. توزیع‌کننده پیش‌فرض Dispatchers.Main.immediate است که کد را در نخ اصلی بدون توزیع‌بندی اضافی اجرا می‌کند، اگر فراخوانی از قبل روی Main باشد.

چگونه viewModelScope از پاک‌سازی مطلع می‌شود

وقتی ViewModel چرخه حیات را ترک می‌کند (Activity پایان یافته یا Fragment حذف شده)، سیستم clear() را فراخوانی می‌کند که onCleared() را فعال می‌کند. در این فراخوانی viewModelScope Job خود را لغو می‌کند که به صورت بازگشتی تمام کوروتین‌های فعال را خاتمه می‌دهد. این مکانیزم از طریق رابط Closeable پیاده‌سازی شده است، جایی که Job scope به عنوان منبعی برای بستن خودکار ثبت می‌شود.

viewModelScope چگونه کار می‌کند: اتصال به چرخه حیات ViewModel

مکانیزم اتصال viewModelScope به چرخه حیات ViewModel بر اساس برچسب‌گذاری و فراخوانی onCleared است. بیایید گام به گام نحوه کار آن را بررسی کنیم.

گام 1: ایجاد scope در اولین دسترسی

وقتی ViewModel دستور viewModelScope.launch { ... } را اجرا می‌کند، getter بررسی می‌کند که آیا scope ذخیره‌شده با برچسب JOB_KEY وجود دارد یا خیر. اگر scope هنوز ایجاد نشده باشد — یک نمونه جدید از CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate) ایجاد می‌شود. Scope در داخل ViewModel از طریق یک نقشه داخلی از برچسب‌ها ذخیره می‌شود.

گام 2: چرخه حیات کوروتین‌ها

تمام کوروتین‌های راه‌اندازی‌شده از طریق viewModelScope.launch یا viewModelScope.async نسبت به SupervisorJob scope فرزند می‌شوند. آنها روی نخ اصلی کار می‌کنند (اگر توزیع‌کننده دیگری از طریق withContext مشخص نشده باشد). تا زمانی که ViewModel زنده است — کوروتین‌ها می‌توانند فعال، معلق یا خاتمه‌یافته باشند.

گام 3: لغو در onCleared

وقتی سیستم ViewModel را نابود می‌کند، ViewModel.clear() فراخوانی می‌شود. درون clear() موارد زیر رخ می‌دهد:

  • فراخوانی onCleared() برای منطق کاربر
  • بستن تمام منابع Closeable ثبت‌شده از طریق addCloseable
  • Job viewModelScope به حالت Cancelled منتقل می‌شود
  • تمام کوروتین‌های فرزند به صورت بازگشتی لغو می‌شوند
  • آزادسازی ارجاعات به scope برای جمع‌آوری زباله

مقاومت در برابر چرخش صفحه

هنگام چرخش صفحه، Activity بازآفرینی می‌شود، اما ViewModel حفظ می‌شود (به لطف ViewModelStoreOwner). این بدان معناست که viewModelScope فعال می‌ماند و کوروتین‌ها بدون وقفه به کار خود ادامه می‌دهند. پس از بازآفرینی Activity، همان ViewModel (و همان scope) دوباره استفاده می‌شود — بارگذاری داده از نو شروع نمی‌شود.

viewModelScope در معماری MVVM

MVVM (Model-View-ViewModel) — معماری توصیه‌شده توسط Google برای برنامه‌های Android است. viewModelScope در این معماری به عنوان مجری عملیات‌های ناهمگام جایگاه مرکزی دارد.

نقش viewModelScope در لایه‌های معماری

لایهکامپوننتنقش viewModelScope
UIActivity / FragmentStateFlow/LiveData از ViewModel را مشاهده می‌کند
ViewModelViewModelکوروتین‌ها را از طریق viewModelScope راه‌اندازی می‌کند، وضعیت UI را مدیریت می‌کند
RepositoryRepositoryتابع‌های suspend فراخوانی‌شده از کوروتین‌های viewModelScope را دریافت می‌کند
DataDAO / Apiدرخواست‌های واقعی را اجرا می‌کند (Room, Retrofit)

ViewModel از طریق viewModelScope کوروتین‌هایی را راه‌اندازی می‌کند که درون آنها تابع‌های suspend Repository را فراخوانی می‌کند. نتیجه به StateFlow تبدیل می‌شود که توسط لایه UI مشاهده می‌شود. این طرح جداسازی واضح مسئولیت‌ها و قابلیت تست هر لایه را به طور مستقل تضمین می‌کند.

چرا viewModelScope در ViewModel، نه در Fragment

اگر کوروتین‌ها از Fragment راه‌اندازی می‌شدند، هنگام چرخش صفحه همراه با نابودی Fragment لغو می‌شدند. ViewModel از چرخش جان سالم به در می‌برد، بنابراین کوروتین‌های راه‌اندازی‌شده در scope آن به کار خود ادامه می‌دهند. این مزیت کلیدی viewModelScope نسبت به lifecycleScope هنگام بارگذاری داده است.

نمونه‌های استفاده از viewModelScope

سه سناریوی عملی استفاده از viewModelScope در برنامه Android به زبان Kotlin را بررسی می‌کنیم.

مثال 1: بارگذاری داده هنگام ایجاد ViewModel

kotlin
class ProfileViewModel(
    private val repo: ProfileRepository
) : ViewModel() {

    private val _profile = MutableStateFlow<Profile?>(null)
    val profile: StateFlow<Profile?> = _profile

    init {
        loadProfile()
    }

    private fun loadProfile() {
        viewModelScope.launch {
            val result = repo.getProfile()
            _profile.value = result
        }
    }
}

در بلوک init بلافاصله بارگذاری پروفایل آغاز می‌شود. کوروتین به طور پیش‌فرض روی نخ اصلی اجرا می‌شود. Repository برای درخواست شبکه درون تابع suspend خود از withContext(Dispatchers.IO) استفاده می‌کند، بنابراین ViewModel نگران جابجایی نخ‌ها نیست.

مثال 2: مدیریت خطا از طریق sealed class

kotlin
sealed class UiState {
    object Loading : UiState()
    data class Success(val data: List<Item>) : UiState()
    data class Error(val message: String) : UiState()
}
kotlin
fun fetchItems() {
    _state.value = UiState.Loading
    viewModelScope.launch {
        try {
            val items = repo.getItems()
            _state.value = UiState.Success(items)
        } catch (e: Exception) {
            _state.value = UiState.Error(e.message ?: "Unknown error")
        }
    }
}

وضعیت UI از طریق sealed class UiState توصیف می‌شود. ViewModel state را در هر تغییر به‌روزرسانی می‌کند. Fragment روی StateFlow مشترک شده و فقط به وضعیت جاری واکنش نشان می‌دهد و فراخوانی‌های قدیمی در چرخش‌های مکرر را نادیده می‌گیرد.

مثال 3: لغو کوروتین قبلی هنگام درخواست جدید

kotlin
private var searchJob: Job? = null

fun search(query: String) {
    searchJob?.cancel()
    searchJob = viewModelScope.launch {
        delay(300)
        val results = repo.search(query)
        _searchResults.value = results
    }
}

در هر درخواست جستجوی جدید، کوروتین قبلی لغو می‌شود. delay(300) debounce را پیاده‌سازی می‌کند — جستجو فقط پس از 300 میلی‌ثانیه مکث در ورودی انجام می‌شود. این کار بار سرور را کاهش می‌دهد و از نتایج قدیمی جلوگیری می‌کند.

viewModelScope در مقابل lifecycleScope: چه زمانی کدام را انتخاب کنیم

هر دو scope توسط کتابخانه AndroidX Lifecycle ارائه می‌شوند، اما به چرخه‌های حیات متفاوتی متصل هستند. انتخاب بین آنها به نوع وظیفه بستگی دارد.

مقایسه scopeها

ویژگیviewModelScopelifecycleScope
مالکViewModelLifecycleOwner (Activity/Fragment)
لغو در چرخشخیر (ViewModel حفظ می‌شود)بله (Activity بازآفرینی می‌شود)
توزیع‌کننده پیش‌فرضDispatchers.Main.immediateDispatchers.Main.immediate
در دسترس درViewModelActivity, Fragment, Service
سناریوی معمولبارگذاری داده، منطق کسب‌وکارتعامل UI، انیمیشن‌ها، اسنک‌بارها

توصیه‌های Google

Google استفاده از viewModelScope را برای تمام وظایف مرتبط با بارگذاری و پردازش داده توصیه می‌کند. lifecycleScope باید برای عملیات‌های متصل به لحظه خاصی از عمر UI استفاده شود — مانند راه‌اندازی انیمیشن در اولین نمایش صفحه یا اشتراک در به‌روزرسانی‌های Location که باید با خروج از صفحه متوقف شود.

خطاهای رایج هنگام کار با viewModelScope

حتی در API مستند Android، توسعه‌دهندگان مرتکب خطاهای مشخصی می‌شوند. چهار مشکل رایج را بررسی می‌کنیم.

خطای 1: به‌روزرسانی UI پس از لغو scope

موذی‌ترین خطا — تلاش برای به‌روزرسانی StateFlow یا LiveData پس از پاک‌سازی ViewModel. اگرچه viewModelScope در onCleared() لغو می‌شود، کوروتین ممکن است کد را تا لحظه لغو واقعی اجرا کند. برای بررسی از isActive استفاده کنید یا به اتمام بلوک catch اعتماد کنید.

خطای 2: راه‌اندازی کوروتین‌ها بدون در نظر گرفتن SupervisorJob

viewModelScope در داخل از SupervisorJob استفاده می‌کند که خطاها را بین کوروتین‌ها ایزوله می‌کند. اما اگر کوروتینی با Job() خود درون viewModelScope.launch راه‌اندازی کنید، آن کوروتین فرزند SupervisorJob می‌شود، اما در برابر لغو هنگام خطا در کوروتین‌های دیگر محافظت نخواهد شد.

خطای 3: تعداد زیاد کوروتین در یک scope

اگرچه viewModelScope محدودیت سخت‌افزاری ندارد، هزاران کوروتین فعال می‌توانند سیستم را کند کنند. برای لیست‌های طولانی داده به جای ایجاد کوروتین جداگانه برای هر عنصر از Flow با collectLatest استفاده کنید.

خطای 4: استفاده از GlobalScope به جای viewModelScope

اگر به طور تصادفی به جای viewModelScope از GlobalScope وارد کنید، کوروتین هنگام پاک‌سازی ViewModel لغو نخواهد شد. این منجر به نشت حافظه و احتمالاً کرش می‌شود. همیشه بررسی کنید که کوروتین‌ها از طریق viewModelScope راه‌اندازی می‌شوند، به ویژه در fragmentهای مشتق‌شده.

سوالات متداول

آیا می‌توانتوزیع‌کننده پیش‌فرض viewModelScope را تغییر داد؟

تغییر مستقیمتوزیع‌کننده viewModelScope ممکن نیست — آن به صورت سخت‌افزاری به عنوان Dispatchers.Main.immediate تنظیم شده است. اما درون کوروتین می‌توان از طریق withContext بهتوزیع‌کننده دیگری سوئیچ کرد. برای تغییرتوزیع‌کننده در تست‌ها از TestDispatcher از طریق Rule استفاده کنید.

چگونه viewModelScope را به Repository منتقل کنیم؟

scope را به Repository منتقل نکنید — این اصول معماری را نقض می‌کند. Repository باید تابع‌های suspend ارائه دهد و ViewModel خود کوروتین‌ها را از طریق viewModelScope مدیریت کند. اگر Repository نیاز به scope دارد — معماری را به نفع Clean Architecture بازبینی کنید.

چرا viewModelScope از SupervisorJob استفاده می‌کند؟

SupervisorJob تضمین می‌کند که استثنا در یک کوروتین (مثلاً خطای بارگذاری یکی از چندین درخواست مستقل) بقیه کوروتین‌ها را لغو نمی‌کند. این با سناریوی ViewModel مطابقت دارد، جایی که صفحات مختلف داده‌های مستقل بارگذاری می‌کنند.

آیا viewModelScope در Jetpack Compose در دسترس است؟

بله، viewModelScope بدون توجه به نوع UI (View System یا Jetpack Compose) در هر ViewModel در دسترس است. در Compose نیز کوروتین‌ها از طریق viewModelScope راه‌اندازی می‌شوند و برای اثرات UI از LaunchedEffect و rememberCoroutineScope استفاده می‌شود.

با کوروتین هنگام فراخوانی viewModelScope.cancel() چه اتفاقی می‌افتد؟

فراخوانی viewModelScope.cancel() scope را فوراً لغو می‌کند — تمام کوروتین‌های فعال با CancellationException خاتمه می‌یابند. اگر پس از آن viewModelScope.launch را فراخوانی کنید، scope جدید در دسترسی بعدی به getter به طور خودکار ایجاد می‌شود.

خلاصه

  • viewModelScope — CoroutineScope متصل به چرخه حیات ViewModel و به طور خودکار در onCleared() لغو می‌شود
  • SupervisorJob + Dispatchers.Main — پیکربندی داخلی که ایزوله‌سازی خطاها و دسترسی ایمن به UI را تضمین می‌کند
  • چرخش صفحه — ViewModel حفظ می‌شود، بنابراین کوروتین‌ها در viewModelScope بدون راه‌اندازی مجدد به کار ادامه می‌دهند
  • معماری MVVM — viewModelScope عنصر مرکزی برای عملیات‌های ناهمگام در لایه ViewModel است
  • lifecycleScope — جایگزینی برای عملیات‌های متصل به چرخه حیات Activity/Fragment، نه ViewModel
  • StateFlow — روش ترجیحی ارسال داده از کوروتین‌های viewModelScope به UI از طریق sealed class
  • GlobalScope خطرناک است — جایگزینی viewModelScope با GlobalScope منجر به نشت حافظه و کرش برنامه می‌شود

ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد

IT Sectr از سال 2017 برنامه‌های iOS و Android را برای استارتاپ‌ها و کسب‌وکارها ایجاد می‌کند. ما به شما مشاوره می‌دهیم و بهترین راه‌حل را پیشنهاد خواهیم کرد.

بحث درباره پروژه

همچنین بخوانید