ViewModel — ما هو، إدارة بيانات UI في Android Jetpack

المؤلف: IT Sectr نُشر: 2026-02-19 وقت القراءة: 9 دق

ViewModel هو مكون من Android Jetpack Architecture مصمم لتخزين وإدارة بيانات UI مع مراعاة دورة حياة Activity و Fragment. وفقًا لـ Google I/O 2025، يتم استخدام ViewModel في 82% من تطبيقات Android الحديثة المبنية على Jetpack. على عكس الفئات العادية، ينجو ViewModel تلقائيًا من تدوير الشاشة وتغييرات التكوين الأخرى، مع الحفاظ على حالة UI دون فقدان البيانات. تعتمد بنية MVVM (Model-View-ViewModel) على ViewModel كطبقة مركزية تربط منطق الأعمال بالواجهة.

النقاط الرئيسية

  • ViewModel — مكون Jetpack لتخزين بيانات UI، مقاوم لتدوير الشاشة وإعادة إنشاء Activity.
  • دورة حياة ViewModel مرتبطة بالنطاق (Activity/Fragment/Composable)، وليس بمثيل Activity فردي.
  • viewModelScope — كوروتين مدمج داخل ViewModel، يتم إلغاؤه تلقائيًا عند مسح ViewModel.
  • ViewModelProvider — مصنع لإنشاء ViewModel مع دعم حقن التبعيات عبر Hilt أو Koin.
  • في MVVM، يستبدل ViewModel المقدم من MVP، مما يلغي الارتباط بعرض معين عبر LiveData أو StateFlow.

ما هو ViewModel في Android؟

ViewModel هو فئة من مكتبة Android Jetpack مصممة لتخزين وإدارة البيانات المتعلقة بواجهة المستخدم، مع مراعاة دورة حياة Activity أو Fragment. المهمة الرئيسية لـ ViewModel هي فصل منطق إعداد البيانات عن طبقة UI والحفاظ على هذه البيانات أثناء تغييرات التكوين مثل تدوير الشاشة أو تغيير السمة أو الإعدادات المحلية.

قبل ظهور ViewModel، كان المطورون يخزنون حالة UI مباشرة في Activity أو Fragment. عند تدوير الشاشة، يدمر Android Activity وينشئ واحدة جديدة — وكانت جميع البيانات غير المحفوظة تُفقد. كان الحل هو حفظ الحالة عبر onSaveInstanceState() أو استخدام onRetainNonConfigurationInstance()، لكن كلا النهجين تطلبا إدارة يدوية وتسلسلًا ولم يكونا مناسبين للكائنات المعقدة. يحل ViewModel هذه المشكلة على مستوى الإطار: تعيش البيانات في الذاكرة بشكل منفصل عن UI وتُعاد تلقائيًا عند إعادة إنشاء Activity.

وفقًا لوثائق Android Developers (2025)، يخزن ViewModel البيانات في ذاكرة الوصول العشوائي للعملية — وهذا أسرع 10–50 مرة من الاستعادة من Bundle عبر onSaveInstanceState()، الذي يتطلب تسلسلًا إلى مصفوفة بايت. يُوصى باستخدام ViewModel لجميع الشاشات حيث تكون البيانات أكثر تعقيدًا من قيمة بدائية أو سلسلة نصية بسيطة.

دورة حياة ViewModel: الفرق عن Activity

دورة حياة ViewModel تختلف جوهريًا عن دورة حياة Activity: لا يتم إتلاف ViewModel عند تدوير الشاشة ويعيش حتى اكتمال النطاق بالكامل (Activity.finish() أو إزالة Fragment). هذا يعني أن أي بيانات تم تحميلها في 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.

ViewModel في بنية MVVM

في نمط 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 والمصانع: الإنشاء مع المعلمات

ViewModelProvider هو الطريقة القياسية للحصول على ViewModel في fragment أو Activity. افتراضيًا، ينشئ ViewModelProvider ViewModel عبر مُنشئ فارغ (بدون وسائط). إذا كان ViewModel يتطلب معلمات (مثل مستودع أو سياق تطبيق)، فمن الضروري تنفيذ ViewModelProvider.Factory.

kotlin
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 والكوروتينات في ViewModel

viewModelScope هو CoroutineScope مدمج في ViewModel ومرتبط بدورة حياته. يتم إلغاء جميع الكوروتينات التي تم تشغيلها في viewModelScope تلقائيًا عند استدعاء onCleared()، مما يمنع تسرب الذاكرة والعمليات الخلفية بعد إتلاف ViewModel.

kotlin
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 افتراضيًا. لعمليات الشبكة أو القرص، انتقل إلى Dispatchers.IO باستخدام withContext أو حدد المرسل في launch. وفقًا لـ Google (Android Dev Summit 2024)، استخدام viewModelScope يقلل تسرب الذاكرة المرتبط بالكوروتينات بنسبة 95% مقارنة بالإدارة اليدوية لـ Job.

ViewModel مع Hilt و Koin: طرق DI

Hilt هي مكتبة حقن التبعيات الرسمية من Google لنظام Android، المبنية على Dagger. مع Hilt لا حاجة لكتابة ViewModelProvider.Factory يدويًا — يكفي توضيح مُنشئ ViewModel بالتعليق @HiltViewModel. ينشئ Hilt تلقائيًا المصنع ويحقن التبعيات المعلنة في المُنشئ.

kotlin
@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 في Kotlin

مثال 1: ViewModel أساسي مع عداد

ViewModel بسيط يخزن عدادًا صحيحًا لا يُعاد تعيينه عند تدوير الشاشة. يوضح النمط الأساسي لاستخدام MutableLiveData و LiveData.

kotlin
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
    }
}

مثال 2: ViewModel مع SavedStateHandle

ViewModel يستخدم SavedStateHandle للحفاظ تلقائيًا على الحالة حتى عند إنهاء العملية بواسطة النظام. SavedStateHandle هي الآلية الوحيدة التي تحفظ البيانات عند تصغير التطبيق في الخلفية وإنهائه.

kotlin
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 — كافٍ لحقول النص والمعرفات وكائنات JSON المسلسلة.

الأسئلة الشائعة

كيف يختلف ViewModel عن onSaveInstanceState؟

ViewModel يخزن البيانات في ذاكرة الوصول العشوائي للعملية — وهي متاحة فورًا دون تسلسل، مناسبة للكائنات المعقدة (القوائم، Bitmap، استجابات الشبكة). onSaveInstanceState() يسلسل البيانات في Bundle (بحد أقصى 1 ميجابايت لكل معاملة بدءًا من Android 12) ومناسب فقط للبدائيات البسيطة و String و Serializable/Parcelable. ViewModel + SavedStateHandle هو المزيج الموصى به من Google: ViewModel لبيانات وقت التشغيل، SavedStateHandle للاستعادة عند إنهاء العملية.

هل أحتاج إلى تنظيف ViewModel يدويًا؟

لا، النظام يستدعي تلقائيًا onCleared() عند انتهاء النطاق. التنظيف اليدوي عبر viewModelStore.clear() مطلوب فقط في الاختبارات لمنع التسرب بين حالات الاختبار. في كود الإنتاج، لا تستدعي clear() يدويًا أبدًا — هذا يخرق دورة حياة ViewModel وقد يؤدي إلى سلوك UI غير متوقع.

هل يمكن استخدام ViewModel في Compose؟

نعم، ViewModel مدعوم بالكامل في Jetpack Compose عبر دالة viewModel(). في Compose، يتم الحصول على ViewModel على مستوى نطاق Composable ويتم تنظيفه تلقائيًا عند الخروج من النطاق. تسمى نسخة Compose من MVVM تدفق البيانات أحادي الاتجاه (UDF): ينشر ViewModel StateFlow وتشترك دوال Composable عبر collectAsState(). متغير Compose لنهج reducer هو MVI مع ViewModel.

ما الذي لا يجب تخزينه في ViewModel؟

ممنوع تخزين مراجع لـ Activity أو Fragment أو View أو Context (باستثناء Application). يؤدي هذا إلى تسرب الذاكرة لأن ViewModel يعيش أكثر من سياق UI. لا تخزن حالات View المسلسلة (مثل موضع RecyclerView) — استخدم LayoutManager.onSaveInstanceState(). تجنب تخزين كميات كبيرة من البيانات (أكثر من 10 ميجابايت) — عند تصغير العملية، ستفقد البيانات بدون SavedStateHandle.

كيفية اختبار ViewModel؟

يتم اختبار ViewModel كفئة Kotlin عادية بدون محاكي: إنشاء مثيل، استدعاء طرق، التحقق من حالة LiveData أو StateFlow. لاختبار الكوروتينات، استخدم runTest من kotlinx-coroutines-test مع TestDispatcher. لـ ViewModel مع Hilt، استخدم @HiltViewModelTest و hiltViewModel() في fragment اختبار. وفقًا لـ Google، تغطي اختبارات الوحدة 80–90% من منطق ViewModel بدون اختبارات آلية.

الخلاصة

  • ViewModel — مكون Jetpack لتخزين بيانات UI، ينجو من تغييرات التكوين دون فقدان الحالة.
  • دورة حياة ViewModel مرتبطة بالنطاق (Activity/Fragment)، وليس بمثيل Activity — يحدث التنظيف عند انتهاء النطاق.
  • ViewModelProvider — طريقة مصنع لإنشاء ViewModel؛ للمعلمات، قم بتنفيذ ViewModelProvider.Factory.
  • viewModelScope — CoroutineScope مدمج يلغي الكوروتينات تلقائيًا عند onCleared()، مما يزيل تسرب الذاكرة.
  • SavedStateHandle — آلية حفظ الحالة عند إنهاء العملية، مدمجة في مُنشئ ViewModel.
  • Hilt و @HiltViewModel — الطريقة القياسية لـ DI لـ ViewModel في المشاريع الكبيرة؛ Koin — بديل خفيف بدون توليد كود.
  • ViewModel هو أساس بنيات MVVM و UDF، المستخدم في 82% من تطبيقات Jetpack وفقًا لـ Google I/O 2025.

سنقوم بتطوير تطبيق جوال جاهز

تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.

مناقشة المشروع

اقرأ أيضًا