نظرة عامة على ViewModel جزء من Android Jetpack.
الفئة 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 في المرة الأولى التي يستدعي فيها النظام
طريقة 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
نماذج
اقتراحات مخصصة لك
- ملاحظة: يتم عرض نص الرابط عند إيقاف JavaScript
- استخدام كوروتينات Kotlin مع المكوّنات التي تراعي دورة الحياة
- حفظ حالات واجهة المستخدم
- تحميل البيانات المقسَّمة إلى صفحات وعرضها