يناقش دليل طبقة واجهة المستخدم تدفّق البيانات أحادي الاتجاه (UDF) كوسيلة لإنشاء حالة واجهة المستخدم وإدارتها في طبقة واجهة المستخدم.
ويوضّح أيضًا مزايا تفويض إدارة الدوال المعرَّفة من قِبل المستخدم إلى فئة خاصة تُعرف باسم "عنصر الاحتفاظ بالحالة". يمكنك تنفيذ عنصر الاحتفاظ بالحالة إما من خلال
ViewModel أو فئة عادية. يتناول هذا المستند نظرة تفصيلية على عناصر الحالة والدور الذي تؤدّيه في طبقة واجهة المستخدم.
في نهاية هذا المستند، من المفترض أن يكون لديك فهم لكيفية إدارة حالة التطبيق في طبقة واجهة المستخدم، أي مسار إنتاج حالة واجهة المستخدم. يجب أن تكون قادرًا على فهم ما يلي ومعرفته:
- التعرّف على أنواع حالات واجهة المستخدم المتوفّرة في طبقة واجهة المستخدم
- فهم أنواع المنطق التي تعمل على حالات واجهة المستخدم هذه في طبقة واجهة المستخدم
- معرفة كيفية اختيار التنفيذ المناسب لعنصر يحتفظ بالحالة، مثل
ViewModelأو فئة
عناصر مسار إنتاج حالة واجهة المستخدم
تحدّد حالة واجهة المستخدم والمنطق الذي ينتجها طبقة واجهة المستخدم.
حالة واجهة المستخدم
حالة واجهة المستخدم هي السمة التي تصف واجهة المستخدم. هناك نوعان من حالات واجهة المستخدم:
- حالة واجهة مستخدم الشاشة هي ما تحتاج إلى عرضه على الشاشة. على سبيل المثال، يمكن أن يحتوي صف
NewsUiStateعلى المقالات الإخبارية والمعلومات الأخرى اللازمة لعرض واجهة المستخدم. يرتبط هذا الوضع عادةً بطبقات أخرى من التسلسل الهرمي لأنّه يتضمّن بيانات التطبيق. - تشير حالة عنصر في واجهة المستخدم إلى الخصائص المضمّنة في عناصر واجهة المستخدم والتي تؤثر في طريقة عرضها. قد يتم إظهار عنصر في واجهة المستخدم أو إخفاؤه، وقد يكون له خط أو حجم خط أو لون خط معيّن. في Jetpack Compose، تكون الحالة خارجية بالنسبة إلى الدالة المركّبة، ويمكنك حتى نقلها إلى خارج النطاق المباشر للدالة المركّبة إلى الدالة المركّبة التي تستدعيها أو إلى عنصر الاحتفاظ بالحالة. من الأمثلة على ذلك
ScaffoldStateللدالة البرمجية القابلة للإنشاءScaffold.
المنطق
حالة واجهة المستخدم ليست خاصية ثابتة، لأنّ بيانات التطبيق وأحداث المستخدم تؤدي إلى تغيير حالة واجهة المستخدم بمرور الوقت. تحدّد المنطق تفاصيل التغيير، بما في ذلك أجزاء حالة واجهة المستخدم التي تم تغييرها وسبب تغييرها ووقت تغييرها.
يمكن أن تكون المنطق في التطبيق إما منطقًا تجاريًا أو منطقًا خاصًا بواجهة المستخدم:
- المنطق التجاري هو تنفيذ متطلبات المنتج لبيانات التطبيق. على سبيل المثال، إضافة مقالة إلى الإشارات المرجعية في تطبيق قارئ أخبار عندما ينقر المستخدم على الزر. يتم عادةً وضع هذه المنطقية لحفظ إشارة مرجعية في ملف أو قاعدة بيانات في طبقات النطاق أو البيانات. وعادةً ما يفوّض عنصر الاحتفاظ بالحالة هذه المنطق إلى تلك الطبقات من خلال استدعاء الطرق التي تعرضها.
- تتعلّق منطق واجهة المستخدم بكيفية عرض حالة واجهة المستخدم على الشاشة. على سبيل المثال، الحصول على تلميح شريط البحث المناسب عندما يختار المستخدم فئة، أو الانتقال إلى عنصر معيّن في قائمة، أو منطق التنقّل إلى شاشة معيّنة عندما ينقر المستخدم على زر.
دورة حياة Android وأنواع حالة واجهة المستخدم ومنطقها
تتضمّن طبقة واجهة المستخدم جزأين: أحدهما يعتمد على دورة حياة واجهة المستخدم والآخر مستقل عنها. يحدّد هذا الفصل مصادر البيانات المتاحة لكل جزء، وبالتالي يتطلّب أنواعًا مختلفة من حالة واجهة المستخدم ومنطقها.
- دورة حياة مستقلة عن واجهة المستخدم: يتعامل هذا الجزء من طبقة واجهة المستخدم مع الطبقات التي تنتج البيانات في التطبيق (طبقات البيانات أو النطاق) ويتم تحديده من خلال منطق النشاط التجاري. قد تؤثّر دورة الحياة وتغييرات الإعدادات وإعادة إنشاء
Activityفي واجهة المستخدم في ما إذا كان خط إنتاج حالة واجهة المستخدم نشطًا، ولكنّها لا تؤثّر في صلاحية البيانات التي يتم إنتاجها. - دورة حياة واجهة المستخدِم: يتعامل هذا الجزء من طبقة واجهة المستخدِم مع منطق واجهة المستخدِم، ويتأثر بشكل مباشر بدورة الحياة أو تغييرات الإعداد. تؤثّر هذه التغييرات بشكل مباشر في صحة مصادر البيانات التي تتم قراءتها ضمنها، ونتيجةً لذلك، لا يمكن تغيير حالتها إلا عندما تكون دورة حياتها نشطة. وتشمل الأمثلة على ذلك أذونات وقت التشغيل والحصول على موارد تعتمد على الإعدادات، مثل السلاسل المترجمة.
يمكن تلخيص ما سبق باستخدام الجدول أدناه:
| مراحل نشاط واجهة المستخدم المستقلة | تعتمد على مراحل نشاط واجهة المستخدم |
|---|---|
| منطق النشاط التجاري | منطق واجهة المستخدم |
| حالة واجهة المستخدم على الشاشة |
مسار إنتاج حالة واجهة المستخدم
يشير مسار إعداد حالة واجهة المستخدم إلى الخطوات المتّخذة لإعداد حالة واجهة المستخدم. تتضمّن هذه الخطوات تطبيق أنواع المنطق المحدّدة سابقًا، وتعتمد بشكل كامل على احتياجات واجهة المستخدم. قد تستفيد بعض واجهات المستخدم من الأجزاء المستقلة عن مراحل نشاط واجهة المستخدم والأجزاء التابعة لها في مسار التعلّم، أو من أحدهما، أو من كليهما.
أي أنّ التباديل التالية لخطوات معالجة طبقة واجهة المستخدم صالحة:
حالة واجهة المستخدم التي يتم إنتاجها وإدارتها من خلال واجهة المستخدم نفسها على سبيل المثال، عدّاد أساسي بسيط وقابل لإعادة الاستخدام:
@Composable fun Counter() { // The UI state is managed by the UI itself var count by remember { mutableStateOf(0) } Row { Button(onClick = { ++count }) { Text(text = "Increment") } Button(onClick = { --count }) { Text(text = "Decrement") } } }منطق واجهة المستخدم → واجهة المستخدم على سبيل المثال، عرض أو إخفاء زر يسمح للمستخدم بالانتقال إلى أعلى القائمة
@Composable fun ContactsList(contacts: List<Contact>) { val listState = rememberLazyListState() val isAtTopOfList by remember { derivedStateOf { listState.firstVisibleItemIndex < 3 } } // Create the LazyColumn with the lazyListState ... // Show or hide the button (UI logic) based on the list scroll position AnimatedVisibility(visible = !isAtTopOfList) { ScrollToTopButton() } }منطق النشاط التجاري → واجهة المستخدم عنصر في واجهة المستخدم يعرض صورة المستخدم الحالي على الشاشة.
@Composable fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) { // Read screen UI state from the business logic state holder val uiState by viewModel.uiState.collectAsStateWithLifecycle() // Call on the UserAvatar Composable to display the photo UserAvatar(picture = uiState.profilePicture) }منطق النشاط التجاري → منطق واجهة المستخدم → واجهة المستخدم عنصر في واجهة المستخدم يتم تمريره لعرض المعلومات الصحيحة على الشاشة لحالة واجهة مستخدم معيّنة.
@Composable fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) { // Read screen UI state from the business logic state holder val uiState by viewModel.uiState.collectAsStateWithLifecycle() val contacts = uiState.contacts val deepLinkedContact = uiState.deepLinkedContact val listState = rememberLazyListState() // Create the LazyColumn with the lazyListState ... // Perform UI logic that depends on information from business logic if (deepLinkedContact != null && contacts.isNotEmpty()) { LaunchedEffect(listState, deepLinkedContact, contacts) { val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact) if (deepLinkedContactIndex >= 0) { // Scroll to deep linked item listState.animateScrollToItem(deepLinkedContactIndex) } } } }
في حال تطبيق كلا النوعين من المنطق على مسار إنتاج حالة واجهة المستخدم، يجب دائمًا تطبيق منطق النشاط التجاري قبل منطق واجهة المستخدم. محاولة تطبيق منطق النشاط التجاري بعد منطق واجهة المستخدم يعني أنّ منطق النشاط التجاري يعتمد على منطق واجهة المستخدم. توضّح الأقسام التالية سبب حدوث هذه المشكلة من خلال نظرة متعمّقة على أنواع المنطق المختلفة وحاويات الحالة الخاصة بها.
حاملو الصفات ومسؤولياتهم
تتمثّل مسؤولية عنصر الاحتفاظ بالحالة في تخزين الحالة حتى يتمكّن التطبيق من قراءتها. في الحالات التي تتطلّب منطقًا، تعمل كواسطة وتوفّر إمكانية الوصول إلى مصادر البيانات التي تستضيف المنطق المطلوب. بهذه الطريقة، يفوّض عنصر الاحتفاظ بالحالة منطقًا إلى مصدر البيانات المناسب.
ويؤدي ذلك إلى تحقيق المزايا التالية:
- واجهات مستخدم بسيطة: تربط واجهة المستخدم حالتها فقط.
- إمكانية الصيانة: يمكن تكرار المنطق المحدّد في عنصر الاحتفاظ بالحالة بدون تغيير واجهة المستخدم نفسها.
- إمكانية الاختبار: يمكن اختبار واجهة المستخدم ومنطق إنشاء حالتها بشكل مستقل.
- سهولة القراءة: يمكن لقارئي الرمز البرمجي أن يروا بوضوح الاختلافات بين الرمز البرمجي الخاص بعرض واجهة المستخدم والرمز البرمجي الخاص بإنتاج حالة واجهة المستخدم.
بغض النظر عن حجم عنصر واجهة المستخدم أو نطاقه، يرتبط كل عنصر في واجهة المستخدم بعلاقة واحد إلى واحد مع عنصر الاحتفاظ بالحالة المقابل له. بالإضافة إلى ذلك، يجب أن يكون العنصر الحافظ للحالة قادرًا على قبول ومعالجة أي إجراء يتخذه المستخدم قد يؤدي إلى تغيير حالة واجهة المستخدم، ويجب أن ينتج عنه تغيير الحالة اللاحق.
أنواع الجهات التي تحتفظ بالحالة
على غرار أنواع حالة واجهة المستخدم ومنطقها، هناك نوعان من عناصر الاحتفاظ بالحالة في طبقة واجهة المستخدم، ويتم تحديدهما حسب علاقتهما بدورة حياة واجهة المستخدم:
- عنصر الاحتفاظ بحالة منطق النشاط التجاري
- عنصر الاحتفاظ بحالة منطق واجهة المستخدم
تلقي الأقسام التالية نظرة فاحصة على أنواع عناصر الاحتفاظ بالحالة، بدءًا بعنصر الاحتفاظ بالحالة الخاص بمنطق النشاط التجاري.
منطق النشاط التجاري وعنصر الاحتفاظ بالحالة
تعالج أدوات الاحتفاظ بحالة منطق النشاط التجاري أحداث المستخدمين وتحوّل البيانات من طبقات البيانات أو النطاق إلى حالة واجهة المستخدم على الشاشة. لتقديم تجربة مثالية للمستخدم عند مراعاة مراحل نشاط Android وتغييرات إعدادات التطبيق، يجب أن تتضمّن عناصر الاحتفاظ بالحالة التي تستخدم منطق النشاط التجاري الخصائص التالية:
| الخاصية | التفصيل |
|---|---|
| إنشاء حالة واجهة المستخدم | تتحمّل الجهات المسؤولة عن حالات منطق النشاط التجاري مسؤولية إنشاء حالة واجهة المستخدم الخاصة بواجهات المستخدم. غالبًا ما تكون حالة واجهة المستخدم هذه نتيجة لمعالجة أحداث المستخدم وقراءة البيانات من طبقتَي النطاق والبيانات. |
| الاحتفاظ بالبيانات من خلال إعادة إنشاء النشاط | تحتفظ أدوات معالجة الحالة ومنطق الأنشطة التجارية بحالتها وبخطوط أنابيب معالجة الحالة عند Activity إعادة الإنشاء، ما يساعد في توفير تجربة مستخدم سلسة. في الحالات التي يتعذّر فيها الاحتفاظ بعنصر الاحتفاظ بالحالة وإعادة إنشائه (عادةً بعد إيقاف العملية نهائيًا)، يجب أن يتمكّن عنصر الاحتفاظ بالحالة من إعادة إنشاء حالته الأخيرة بسهولة لضمان تجربة مستخدم متّسقة. |
| امتلاك حالة طويلة الأمد | يتم غالبًا استخدام أدوات الاحتفاظ بحالة منطق النشاط التجاري لإدارة الحالة لوجهات التنقّل. ونتيجةً لذلك، غالبًا ما تحتفظ هذه العناصر بحالتها عند إجراء تغييرات في التنقّل إلى أن تتم إزالتها من الرسم البياني للتنقّل. |
| يكون فريدًا لواجهة المستخدم ولا يمكن إعادة استخدامه | تنتج أدوات الاحتفاظ بحالة منطق النشاط التجاري عادةً حالة لوظيفة تطبيق معيّنة، مثل TaskEditViewModel أو TaskListViewModel، وبالتالي لا تكون قابلة للتطبيق إلا على وظيفة التطبيق هذه. يمكن أن يتيح عنصر الاحتفاظ بالحالة نفسه وظائف التطبيق هذه على مختلف أشكال الأجهزة. على سبيل المثال، قد تعيد إصدارات التطبيق المخصّصة للأجهزة الجوّالة والتلفزيون والأجهزة اللوحية استخدام عنصر الاحتفاظ بحالة منطق النشاط التجاري نفسه. |
على سبيل المثال، لنفترض أنّك تريد الانتقال إلى وجهة التنقّل الخاصة بالمؤلف في تطبيق Now in Android:
باعتبارها عنصر الاحتفاظ بالحالة لمنطق الأعمال، تنتج AuthorViewModel حالة واجهة المستخدم في هذه الحالة:
@HiltViewModel
class AuthorViewModel @Inject constructor(
savedStateHandle: SavedStateHandle,
private val authorsRepository: AuthorsRepository,
newsRepository: NewsRepository
) : ViewModel() {
val uiState: StateFlow<AuthorScreenUiState> = …
// Business logic
fun followAuthor(followed: Boolean) {
…
}
}
لاحظ أنّ AuthorViewModel يتضمّن السمات الموضّحة سابقًا:
| الخاصية | التفصيل |
|---|---|
تنتج AuthorScreenUiState |
يقرأ AuthorViewModel البيانات من AuthorsRepository وNewsRepository ويستخدمها لإنتاج AuthorScreenUiState. تطبّق هذه الواجهة أيضًا منطق النشاط التجاري عندما يريد المستخدم متابعة Author أو إلغاء متابعته من خلال تفويض AuthorsRepository. |
| لديه إذن الوصول إلى طبقة البيانات | يتم تمرير مثيل من AuthorsRepository وNewsRepository إليه في الدالة الإنشائية، ما يسمح له بتنفيذ منطق النشاط التجاري الخاص بمتابعة Author. |
الاستمرار في Activity الاستجمام |
بما أنّه يتم تنفيذه باستخدام ViewModel، سيتم الاحتفاظ به عند إعادة إنشاء Activity سريعًا. في حال إيقاف العملية نهائيًا، يمكن قراءة العنصر SavedStateHandle لتوفير الحد الأدنى من المعلومات المطلوبة لاستعادة حالة واجهة المستخدم من طبقة البيانات. |
| يحتوي على حالة طويلة الأمد | يقتصر نطاق ViewModel على الرسم البياني للتنقّل، لذلك ما لم تتم إزالة وجهة المؤلف من الرسم البياني للتنقّل، ستظل حالة واجهة المستخدم في uiState StateFlow محفوظة في الذاكرة. يوفّر استخدام StateFlow أيضًا ميزة تأجيل تطبيق منطق النشاط التجاري الذي ينتج الحالة، لأنّه لا يتم إنتاج الحالة إلا إذا كان هناك جامع لحالة واجهة المستخدم. |
| فريد من نوعه في واجهة المستخدم | لا يمكن استخدام AuthorViewModel إلا في وجهة التنقّل الخاصة بالمؤلف، ولا يمكن إعادة استخدامه في أي مكان آخر. إذا كان هناك أي منطق عمل يتم إعادة استخدامه في وجهات التنقّل، يجب تغليف منطق العمل هذا في مكوّن ذي نطاق على مستوى البيانات أو النطاق. |
ViewModel كعنصر الاحتفاظ بحالة منطق النشاط التجاري
مزايا ViewModels في تطوير تطبيقات Android تجعلها مناسبة لتوفير إمكانية الوصول إلى منطق النشاط التجاري وإعداد بيانات التطبيق لعرضها على الشاشة. وتشمل هذه المزايا ما يلي:
- تستمر العمليات التي يتم تشغيلها بواسطة ViewModels حتى بعد إجراء تغييرات في الإعدادات.
- التكامل مع التنقل:
- تخزّن ذاكرة التخزين المؤقت للتنقّل ViewModels مؤقتًا أثناء عرض الشاشة في الخلف. وهذا مهم لكي تكون البيانات التي تم تحميلها سابقًا متاحة على الفور عند الرجوع إلى وجهتك. ويصعب تنفيذ ذلك باستخدام عنصر الاحتفاظ بالحالة الذي يتّبع مراحل نشاط الشاشة القابلة للإنشاء.
- يتم أيضًا محو ViewModel عند إزالة الوجهة من الخلف، ما يضمن تنظيف حالتك تلقائيًا. يختلف ذلك عن الاستماع إلى عملية التخلص من الدالة المركّبة التي يمكن أن تحدث لأسباب متعددة، مثل الانتقال إلى شاشة جديدة أو بسبب تغيير في الإعدادات أو لأسباب أخرى.
- التكامل مع مكتبات Jetpack الأخرى، مثل Hilt
منطق واجهة المستخدم وعنصر الاحتفاظ بالحالة
منطق واجهة المستخدم هو منطق يعمل على البيانات التي توفّرها واجهة المستخدم نفسها. وقد يكون ذلك
في حالة عناصر واجهة المستخدم أو في مصادر بيانات واجهة المستخدم، مثل واجهة برمجة التطبيقات الخاصة بالأذونات أو
Resources. تحتوي عناصر الاحتفاظ بالحالة التي تستخدم منطق واجهة المستخدم عادةً على الخصائص التالية:
- تنتج حالة واجهة المستخدم وتدير حالة عناصر واجهة المستخدم.
- لا يمكن الاحتفاظ به عند إعادة إنشاء
Activity: غالبًا ما تعتمد عناصر الاحتفاظ بالحالة المستضافة في منطق واجهة المستخدم على مصادر البيانات من واجهة المستخدم نفسها، ومحاولة الاحتفاظ بهذه المعلومات عند حدوث تغييرات في الإعدادات تؤدي في أغلب الأحيان إلى حدوث تسرُّب للذاكرة. إذا كان مالكو الحالة بحاجة إلى استمرار البيانات عند حدوث تغييرات في الإعدادات، عليهم تفويضها إلى مكوّن آخر أكثر ملاءمةً للاستمرار بعد إعادة الإنشاء.Activityفي Jetpack Compose مثلاً، غالبًا ما يتم تفويض حالات عناصر واجهة المستخدم القابلة للإنشاء التي تم إنشاؤها باستخدام دوالrememberedإلىrememberSaveableللحفاظ على الحالة عند إعادة إنشاءActivity. وتشمل أمثلة هذه الوظائفrememberScaffoldState()وrememberLazyListState(). - يتضمّن مراجع لمصادر البيانات التي يقتصر نطاقها على واجهة المستخدم: يمكن الرجوع إلى مصادر البيانات، مثل واجهات برمجة التطبيقات لدورة الحياة والموارد، وقراءتها بأمان لأنّ العنصر الحافظ لحالة منطق واجهة المستخدم له دورة الحياة نفسها التي تتشاركها واجهة المستخدم.
- إمكانية إعادة الاستخدام في عدة واجهات مستخدم: يمكن إعادة استخدام مثيلات مختلفة من عنصر الاحتفاظ بالحالة نفسه الذي يحتفظ بحالة منطق واجهة المستخدم في أجزاء مختلفة من التطبيق. على سبيل المثال، يمكن استخدام عنصر الاحتفاظ بالحالة لإدارة بيانات أدخلها المستخدمين لمجموعة شرائح في صفحة بحث لشرائح الفلتر، وكذلك للحقل "إلى" الخاص بمستلمي رسالة إلكترونية.
عادةً ما يتم تنفيذ عنصر الاحتفاظ بحالة منطق واجهة المستخدم باستخدام فئة عادية. ويرجع ذلك إلى أنّ واجهة المستخدم نفسها مسؤولة عن إنشاء عنصر الاحتفاظ بحالة منطق واجهة المستخدم، كما أنّ عنصر الاحتفاظ بحالة منطق واجهة المستخدم له مراحل النشاط نفسها التي تتشاركها واجهة المستخدم. في Jetpack Compose مثلاً، يشكّل عنصر الاحتفاظ بالحالة جزءًا من عملية الإنشاء ويتبع مراحلها.
يمكن توضيح ما سبق في المثال التالي في تطبيق Now in Android النموذجي:
يعرض تطبيق Now in Android النموذجي إما شريط تطبيق سفليًا أو شريط تنقّل للتنقل، وذلك حسب حجم شاشة الجهاز. تستخدم الشاشات الأصغر حجمًا شريط التطبيق السفلي، بينما تستخدم الشاشات الأكبر حجمًا شريط التنقّل الجانبي.
بما أنّ منطق تحديد عنصر في واجهة المستخدم للتنقّل المناسب المستخدَم في الدالة المركّبة NiaApp لا يعتمد على منطق النشاط التجاري، يمكن إدارته من خلال عنصر بسيط للاحتفاظ بحالة الفئة يُسمى NiaAppState:
@Stable
class NiaAppState(
val navController: NavHostController,
val windowSizeClass: WindowSizeClass
) {
// UI logic
val shouldShowBottomBar: Boolean
get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact
// UI logic
val shouldShowNavRail: Boolean
get() = !shouldShowBottomBar
// UI State
val currentDestination: NavDestination?
@Composable get() = navController
.currentBackStackEntryAsState().value?.destination
// UI logic
fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }
/* ... */
}
في المثال السابق، يمكن ملاحظة التفاصيل التالية بشأن NiaAppState:
- لا يبقى بعد إعادة إنشاء
Activity: يكونNiaAppStaterememberedفي التركيب من خلال إنشائه باستخدام دالة مركّبةrememberNiaAppStateباتّباع اصطلاحات التسمية في Compose. بعد إعادة إنشاءActivity، يتم فقدان المثيل السابق ويتم إنشاء مثيل جديد مع تمرير جميع التبعيات فيه، وهو ما يناسب الإعداد الجديد لـActivityالذي تمت إعادة إنشائه. وقد تكون هذه التبعيات جديدة أو تم استعادتها من الإعداد السابق. على سبيل المثال، يتم استخدامrememberNavController()في الدالة الإنشائيةNiaAppState، ويتم تفويضها إلىrememberSaveableللحفاظ على الحالة عند إعادة إنشاءActivity. - يتضمّن مراجع إلى مصادر بيانات ذات نطاق واجهة مستخدم: يمكن الاحتفاظ بأمان بمراجع إلى
navigationControllerوResourcesوأنواع أخرى مشابهة ذات نطاق دورة حياة فيNiaAppStateلأنّها تشترك في نطاق دورة الحياة نفسه.
الاختيار بين ViewModel وفئة عادية لعنصر الاحتفاظ بالحالة
من الأقسام السابقة، يعتمد الاختيار بين ViewModel وعنصر الاحتفاظ بالحالة العادي على المنطق المطبَّق على حالة واجهة المستخدم ومصادر البيانات التي يعمل عليها المنطق.
باختصار، يوضّح المخطّط البياني التالي موضع عناصر الاحتفاظ بالحالة في مسار إنتاج الحالة لواجهة المستخدم:
في النهاية، يجب إنشاء حالة واجهة المستخدم باستخدام عناصر الاحتفاظ بالحالة الأقرب إلى المكان الذي يتم فيه استخدامها. بشكل أقل رسمية، يجب أن يكون مستوى الحالة منخفضًا قدر الإمكان مع الحفاظ على الملكية المناسبة. إذا كنت بحاجة إلى الوصول إلى منطق النشاط التجاري، وإبقاء حالة واجهة المستخدم ثابتة طالما يمكن الانتقال إلى شاشة، حتى عند إعادة إنشاء Activity، فإنّ ViewModel هو خيار رائع لتنفيذ عنصر الاحتفاظ بالحالة لمنطق النشاط التجاري. بالنسبة إلى حالة واجهة المستخدم ومنطق واجهة المستخدم اللذين لا يدومان طويلاً، يكفي استخدام فئة عادية تعتمد دورة حياتها على واجهة المستخدم فقط.
يمكن دمج عناصر الاحتفاظ بالحالة
يمكن أن تعتمد أدوات الاحتفاظ بالحالة على أدوات أخرى للاحتفاظ بالحالة طالما أنّ مدة صلاحية العناصر التابعة تساوي مدة صلاحية العناصر الأساسية أو أقصر منها. ومن الأمثلة على ذلك:
- يمكن أن يعتمد عنصر الاحتفاظ بحالة منطق واجهة المستخدم على عنصر احتفاظ بحالة منطق واجهة مستخدم آخر.
- يمكن أن يعتمد عنصر الاحتفاظ بالحالة على مستوى الشاشة على عنصر الاحتفاظ بالحالة لمنطق واجهة المستخدم.
يوضّح مقتطف الرمز التالي كيف تعتمد DrawerState في Compose على عنصر الاحتفاظ بالحالة الداخلي الآخر، وهو SwipeableState، وكيف يمكن أن يعتمد عنصر الاحتفاظ بالحالة الخاص بمنطق واجهة المستخدم في التطبيق على DrawerState:
@Stable
class DrawerState(/* ... */) {
internal val swipeableState = SwipeableState(/* ... */)
// ...
}
@Stable
class MyAppState(
private val drawerState: DrawerState,
private val navController: NavHostController
) { /* ... */ }
@Composable
fun rememberMyAppState(
drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
MyAppState(drawerState, navController)
}
من الأمثلة على التبعيات التي تدوم لفترة أطول من عنصر الاحتفاظ بالحالة، عنصر الاحتفاظ بحالة منطق واجهة المستخدم الذي يعتمد على عنصر الاحتفاظ بالحالة على مستوى الشاشة. سيؤدي ذلك إلى تقليل إمكانية إعادة استخدام عنصر الاحتفاظ بالحالة الذي لا يدوم طويلاً، كما سيمنحها إمكانية الوصول إلى المزيد من المنطق والحالة أكثر مما تحتاج إليه في الواقع.
إذا كان عنصر الاحتفاظ بالحالة ذو العمر الأقصر يحتاج إلى معلومات معيّنة من عنصر الاحتفاظ بالحالة ذي نطاق أوسع، مرِّر المعلومات التي يحتاجها فقط كمَعلمة بدلاً من تمرير مثيل عنصر الاحتفاظ بالحالة. على سبيل المثال، في مقتطف الرمز التالي، يتلقّى عنصر الاحتفاظ بالحالة لمنطق واجهة المستخدم ما يحتاج إليه فقط كمعلَمات من ViewModel، بدلاً من تمرير مثيل ViewModel بالكامل كاعتمادية.
class MyScreenViewModel(/* ... */) {
val uiState: StateFlow<MyScreenUiState> = /* ... */
fun doSomething() { /* ... */ }
fun doAnotherThing() { /* ... */ }
// ...
}
@Stable
class MyScreenState(
// DO NOT pass a ViewModel instance to a plain state holder class
// private val viewModel: MyScreenViewModel,
// Instead, pass only what it needs as a dependency
private val someState: StateFlow<SomeState>,
private val doSomething: () -> Unit,
// Other UI-scoped types
private val scaffoldState: ScaffoldState
) {
/* ... */
}
@Composable
fun rememberMyScreenState(
someState: StateFlow<SomeState>,
doSomething: () -> Unit,
scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
MyScreenState(someState, doSomething, scaffoldState)
}
@Composable
fun MyScreen(
modifier: Modifier = Modifier,
viewModel: MyScreenViewModel = viewModel(),
state: MyScreenState = rememberMyScreenState(
someState = viewModel.uiState.map { it.toSomeState() },
doSomething = viewModel::doSomething
),
// ...
) {
/* ... */
}
يوضّح المخطّط التالي التبعيات بين واجهة المستخدم ومختلف عناصر الاحتفاظ بالحالة في مقتطف الرمز البرمجي السابق:
نماذج
توضّح نماذج Google التالية كيفية استخدام عناصر الاحتفاظ بالحالة في طبقة واجهة المستخدم. يمكنك استكشافها للاطّلاع على هذه الإرشادات عمليًا:
اقتراحات مخصصة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون JavaScript غير مفعّلة.
- طبقة واجهة المستخدم
- إعداد حالة واجهة المستخدم
- دليل هندسة التطبيق