نظرة عامة على ViewModel   جزء من Android Jetpack.

تجربة Kotlin Multiplatform
تسمح Kotlin Multiplatform بمشاركة منطق النشاط التجاري مع منصات أخرى. كيفية إعداد ViewModel واستخدامه في KMP

الفئة ViewModel هي عنصر احتفاظ بحالة منطق النشاط التجاري أو حالة الشاشة. تعرض هذه الفئة الحالة على واجهة المستخدم وتغلّف منطق النشاط التجاري ذي الصلة. تتمثّل ميزتها الرئيسية في أنّها تخزّن الحالة مؤقتًا وتحتفظ بها عند إجراء تغييرات في الإعداد. يعني ذلك أنّه ليس على واجهة المستخدم جلب البيانات مرة أخرى عند التنقّل بين الأنشطة أو بعد إجراء تغييرات في الإعداد، مثل تدوير الشاشة.

لمزيد من المعلومات عن عناصر الاحتفاظ بالحالة، يُرجى الاطّلاع على إرشادات عناصر الاحتفاظ بالحالة. وبالمثل، لمزيد من المعلومات عن طبقة واجهة المستخدم بشكل عام، يُرجى الاطّلاع على إرشادات طبقة واجهة المستخدم.

مزايا ViewModel

البديل عن ViewModel هو فئة عادية تحتفظ بالبيانات التي تعرضها في واجهة المستخدم. يمكن أن يصبح ذلك مشكلة عند التنقّل بين الأنشطة أو وجهات التنقّل. يؤدي ذلك إلى حذف هذه البيانات إذا لم تخزّنها باستخدام آلية حالة المثيل المحفوظة. توفّر ViewModel واجهة برمجة تطبيقات ملائمة للاحتفاظ بالبيانات تحلّ هذه المشكلة.

بدلاً من ذلك، بالنسبة إلى عناصر الاحتفاظ بالحالة البحتة، يقدّم Compose إمكانات retain تسمح للفئات العادية بالبقاء بعد إجراء تغييرات في الإعداد بدون البنية الأساسية الكاملة لـ ViewModel. على الرغم من أنّ الآليتَين تساعدان في الاحتفاظ بالحالة، من الآمن بشكل عام توفير ViewModel لمثيل محتفظ به بدلاً من العكس، لأنّ دورات حياتهما وسلوكيات التنظيف تختلفان.

الميزتان الرئيسيتان لفئة ViewModel هما:

  • تتيح لك الاحتفاظ بحالة واجهة المستخدم.
  • توفّر إمكانية الوصول إلى منطق النشاط التجاري.

الاستمرارية

تسمح ViewModel بالاستمرارية من خلال كلٍّ من الحالة التي تحتفظ بها ViewModel والعمليات التي تُشغّلها ViewModel. يعني هذا التخزين المؤقت أنّه ليس عليك جلب البيانات مرة أخرى من خلال تغييرات الإعداد الشائعة، مثل تدوير الشاشة.

النطاق

عند إنشاء مثيل ViewModel، يمكنك تمرير عنصر يطبّق واجهة ViewModelStoreOwner. قد يكون هذا العنصر وجهة تنقّل أو رسمًا بيانيًا للتنقّل أو نشاطًا أو أي نوع آخر يطبّق الواجهة. يمكنك أيضًا تحديد نطاق ViewModel مباشرةً إلى دالة مركّبة باستخدام واجهة برمجة التطبيقات rememberViewModelStoreOwner. بعد ذلك، يتم تحديد نطاق ViewModel إلى دورة حياة الـ ViewModelStoreOwner. تظل ViewModel في الذاكرة إلى أن يختفي ViewModelStoreOwner نهائيًا (مثلما يحدث عندما يخرج مالك الدالة المركّبة من التكوين).

هناك مجموعة من الفئات التي تكون فئات فرعية مباشرة أو غير مباشرة لواجهة ViewModelStoreOwner. الفئات الفرعية المباشرة هي ComponentActivity و NavBackStackEntry. للاطّلاع على قائمة كاملة بالفئات الفرعية غير المباشرة، يُرجى الرجوع إلى مرجع ViewModelStoreOwner. لتحديد نطاق ViewModels لعناصر فردية في LazyList أو Pager، استخدِم rememberViewModelStoreProvider() لنقل إدارة المالك إلى العنصر الرئيسي.

عندما يخضع النشاط المضيف لتغيير في الإعداد، يستمر العمل غير المتزامن في ViewModel، سواء كان نطاقه محدّدًا للنشاط أو لدالة مركّبة معيّنة. هذا هو مفتاح الاستمرارية.

لمزيد من المعلومات، يُرجى الاطّلاع على قسم دورة حياة ViewModel أدناه، واجهات برمجة التطبيقات لتحديد نطاق ViewModel، والدليل حول نقل الحالة إلى أعلى في Jetpack Compose.

SavedStateHandle

تسمح لك SavedStateHandle بالاحتفاظ بالبيانات ليس فقط عند إجراء تغييرات في الإعداد ، بل أيضًا عند إيقاف العملية نهائيًا. يعني ذلك أنّها تسمح لك بالحفاظ على حالة واجهة المستخدم سليمة حتى عندما يغلق المستخدم التطبيق ويفتحه في وقت لاحق.

لمزيد من المعلومات عن حفظ حالة واجهة المستخدم، يُرجى الاطّلاع على مقالة حفظ حالة واجهة المستخدم في Compose.

الوصول إلى منطق النشاط التجاري

على الرغم من أنّ الغالبية العظمى من منطق النشاط التجاري موجودة في طبقة البيانات ، يمكن أن تحتوي طبقة واجهة المستخدم أيضًا على منطق النشاط التجاري. يمكن أن يحدث ذلك عند دمج البيانات من مستودّعات متعددة لإنشاء حالة واجهة مستخدم الشاشة، أو عندما لا يتطلّب نوع معيّن من البيانات طبقة بيانات.

ViewModel هي المكان المناسب لمعالجة منطق النشاط التجاري في طبقة واجهة المستخدم. تتولّى ViewModel أيضًا معالجة الأحداث وتفويضها إلى طبقات أخرى من التسلسل الهرمي عندما يجب تطبيق منطق النشاط التجاري لتعديل بيانات التطبيق.

تنفيذ ViewModel

في ما يلي مثال على تنفيذ ViewModel لشاشة تسمح للمستخدم برمي النرد.

data class DiceUiState(
    val firstDieValue: Int? = null,
    val secondDieValue: Int? = null,
    val numberOfRolls: Int = 0,
)

class DiceRollViewModel : ViewModel() {

    // Expose screen UI state
    private val _uiState = MutableStateFlow(DiceUiState())
    val uiState: StateFlow<DiceUiState> = _uiState.asStateFlow()

    // Handle business logic
    fun rollDice() {
        _uiState.update { currentState ->
            currentState.copy(
                firstDieValue = Random.nextInt(from = 1, until = 7),
                secondDieValue = Random.nextInt(from = 1, until = 7),
                numberOfRolls = currentState.numberOfRolls + 1,
            )
        }
    }
}

يمكنك بعد ذلك الوصول إلى ViewModel من دالة مركّبة على مستوى الشاشة على النحو التالي:

import androidx.lifecycle.viewmodel.compose.viewModel

// Use the 'viewModel()' function from the lifecycle-viewmodel-compose artifact
@Composable
fun DiceRollScreen(
    viewModel: DiceRollViewModel = viewModel()
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    // Update UI elements
}

استخدام الكوروتينات مع ViewModel

تتضمّن ViewModel إمكانية استخدام كوروتينات Kotlin. يمكنها الاحتفاظ بالعمل غير المتزامن بالطريقة نفسها التي تحتفظ بها بحالة واجهة المستخدم.

لمزيد من المعلومات، يُرجى الاطّلاع على مقالة استخدام كوروتينات Kotlin مع مكوّنات بنية Android Components.

دورة حياة ViewModel

ترتبط دورة حياة ViewModel مباشرةً بنطاقها. تظل ViewModel في الذاكرة إلى أن يختفي ViewModelStoreOwner الذي تم تحديد نطاقها إليه. قد يحدث ذلك في السياقات التالية:

  • في حالة النشاط، عند انتهائه.
  • في حالة إدخال التنقّل، عند إزالته من الأنشطة السابقة.
  • في حالة الدالة المركّبة، عند الخروج من التكوين. يمكنك استخدام rememberViewModelStoreOwner لتحديد نطاق ViewModel مباشرةً إلى جزء عشوائي من واجهة المستخدم (مثل Pager أو LazyList).

يؤدي ذلك إلى جعل ViewModels حلاً رائعًا لتخزين البيانات التي تبقى بعد إجراء تغييرات في الإعداد.

يوضّح الشكل 1 حالات دورة الحياة المختلفة للنشاط أثناء تدويره ثم انتهائه. يعرض الرسم التوضيحي أيضًا مدة الـ ViewModel بجانب دورة حياة النشاط المرتبطة. يوضّح هذا المخطّط البياني تحديدًا حالات النشاط.

يوضّح هذا الرسم البياني مراحل نشاط ViewModel عند تغيير حالة النشاط.
الشكل 1. حالات دورة حياة النشاط وViewModel

عادةً ما تطلب ViewModel في المرة الأولى التي يستدعي فيها النظام طريقة onCreate() لكائن نشاط. قد يستدعي النظام onCreate() عدة مرات طوال فترة بقاء النشاط، مثل ما يحدث عند تدوير شاشة الجهاز. تكون ViewModel متاحة منذ أول مرة تطلب فيها ViewModel إلى أن ينتهي النشاط ويتم إتلافه.

محو تبعيات ViewModel

تستدعي ViewModel طريقة onCleared عندما يدمّرها ViewModelStoreOwner أثناء دورة حياتها. يسمح لك ذلك بتنظيف أي عمل أو تبعيات تتبع دورة حياة ViewModel.

يعرض المثال التالي بديلاً عن viewModelScope. viewModelScope هي CoroutineScope مضمّنة تتبع تلقائيًا دورة حياة ViewModel. تستخدمها ViewModel لتشغيل العمليات المتعلقة بالنشاط التجاري. إذا كنت تريد استخدام نطاق مخصّص بدلاً من viewModelScope لـ تسهيل الاختبار، يمكن أن تتلقّى ViewModel CoroutineScope كاعتمادية في الدالة الإنشائية. عندما يمحو ViewModelStoreOwner عنصر ViewModel في نهاية دورة حياته، تلغي ViewModel أيضًا CoroutineScope.

class MyViewModel(
    private val coroutineScope: CoroutineScope =
        CoroutineScope(SupervisorJob() + Dispatchers.Main.immediate)
) : ViewModel() {

    // Other ViewModel logic ...

    override fun onCleared() {
        coroutineScope.cancel()
    }
}

بدءًا من الإصدار 2.5 من دورة الحياة والإصدارات الأحدث، يمكنك تمرير عنصر Closeable واحد أو أكثر إلى الدالة الإنشائية لـ ViewModel التي يتم إغلاقها تلقائيًا عند محو مثيل ViewModel.

class CloseableCoroutineScope(
    context: CoroutineContext = SupervisorJob() + Dispatchers.Main.immediate
) : Closeable, CoroutineScope {
    override val coroutineContext: CoroutineContext = context
    override fun close() {
        coroutineContext.cancel()
   }
}

class MyViewModel(
    private val coroutineScope: CoroutineScope = CloseableCoroutineScope()
) : ViewModel(coroutineScope) {
    // Other ViewModel logic ...
}

أفضل الممارسات

في ما يلي العديد من أفضل الممارسات الرئيسية التي يجب اتّباعها عند تنفيذ ViewModel:

  • بسبب تحديد نطاقها، استخدِم ViewModels كتفاصيل تنفيذ لعنصر احتفاظ بحالة على مستوى الشاشة. لا تستخدمها كعناصر احتفاظ بحالة لمكوّنات واجهة مستخدم قابلة لإعادة الاستخدام، مثل مجموعات الشرائح أو النماذج. وإلا، ستحصل على مثيل ViewModel نفسه في استخدامات مختلفة لمكوّن واجهة المستخدم نفسه ضمن `ViewModelStoreOwner` نفسه ما لم تستخدم مفتاح نموذج عرض صريحًا لكل شريحة.
  • يجب ألا تعرف ViewModels تفاصيل تنفيذ واجهة المستخدم. اجعل أسماء الطرق التي تعرضها واجهة برمجة تطبيقات ViewModel وأسماء حقول حالة واجهة المستخدم عامة قدر الإمكان. بهذه الطريقة، يمكن أن تستوعب ViewModel أي نوع من واجهة المستخدم: هاتف جوّال أو جهاز قابل للطي أو جهاز لوحي أو حتى جهاز Chromebook.
  • بما أنّ ViewModels يمكن أن تبقى لفترة أطول من ViewModelStoreOwner، يجب ألا تحتفظ بأي مراجع لواجهات برمجة التطبيقات ذات الصلة بدورة الحياة، مثل Context أو Resources لمنع تسرّب الذاكرة.
  • لا تمرِّر ViewModels إلى فئات أو دوال أو مكوّنات واجهة مستخدم أخرى. بما أنّ النظام الأساسي يديرها، يجب أن تحتفظ بها بالقرب منه قدر الإمكان، أي بالقرب من النشاط أو الدالة القابلة للإنشاء على مستوى الشاشة أو وجهة التنقّل. يمنع ذلك المكوّنات ذات المستوى الأدنى من الوصول إلى بيانات ومنطق أكثر مما تحتاج إليه.

معلومات إضافية

مع ازدياد تعقيد بياناتك، قد تختار استخدام فئة منفصلة فقط لتحميل البيانات. الغرض من ViewModel هو تغلّيف البيانات لـ وحدة تحكّم في واجهة المستخدم للسماح للبيانات بالبقاء بعد إجراء تغييرات في الإعداد. لمعرفة كيفية تحميل البيانات والاحتفاظ بها وإدارتها بعد إجراء تغييرات في الإعداد، يُرجى الاطّلاع على مقالة حالات واجهة المستخدم المحفوظة.

يقترح دليل بنية تطبيقات Android إنشاء فئة مستودع لمعالجة هذه الدوال.

مراجع إضافية

لمزيد من المعلومات عن فئة ViewModel، يُرجى الاطّلاع على المراجع التالية.

الوثائق

محتوى Views

نماذج