في ميزة "الإنشاء"، تكون واجهة المستخدم ثابتة، ولا يمكن تعديلها بعد
سحبها. ما يمكنك التحكّم فيه هو حالة واجهة المستخدم. في كل مرة تتغيّر فيها حالة
واجهة المستخدم، يعيد تطبيق Compose إنشاء أجزاء شجرة واجهة المستخدم التي
تغيّرت. يمكن للعناصر القابلة للتجميع قبول
الحالة وعرض الأحداث. على سبيل المثال، يقبل TextField
قيمة ويعرض
مكالمة استدعاء onValueChange
تطلب من معالِج المكالمة الاستدعاء تغيير
القيمة.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
بما أنّ العناصر القابلة للتجميع تقبل الحالة وتُظهر الأحداث، فإنّ نموذج تدفق البيانات أحادي الاتجاه يناسب Jetpack Compose بشكلٍ جيد. يركز هذا الدليل على كيفية تنفيذ نمط تدفق البيانات أحادي الاتجاه في Compose، وكيفية تنفيذ الأحداث ومقدّمي الحالة، وكيفية العمل مع ViewModels في Compose.
تدفق البيانات أحادي الاتجاه
تدفق البيانات أحادي الاتجاه (UDF) هو نمط تصميم يتدفق فيه الحدث من الأعلى إلى الأسفل ويتدفق الحدث من الأسفل إلى الأعلى. من خلال اتّباع عملية تدفق البيانات أحادية الاتجاه، يمكنك فصل العناصر القابلة للتجميع التي تعرض الحالة في واجهة المستخدم عن أجزاء تطبيقك التي تخزِّن الحالة وتغيّرها.
تبدو حلقة تعديل واجهة المستخدم لتطبيق يستخدم تدفق البيانات أحادي الاتجاه على النحو التالي:
- الحدث: يُنشئ جزء من واجهة المستخدم حدثًا ويمرّره للأعلى، مثل النقر على زر يتم تمريره إلى ViewModel للمعالجة، أو يتم تمرير حدث من طبقات أخرى من تطبيقك، مثل الإشارة إلى أنّ جلسة المستخدم انتهت.
- تعديل الحالة: قد يغيّر معالِج الأحداث الحالة.
- عرض الحالة: ينقل حامل الحالة الحالة، ويعرضها واجهة المستخدم.

يوفّر اتّباع هذا النمط عند استخدام Jetpack Compose العديد من المزايا:
- قابلية الاختبار: إنّ فصل الحالة عن واجهة المستخدم التي تعرضها يسهّل اختبار كليهما بشكل منفصل.
- تجميع الحالات: بما أنّه لا يمكن تعديل الحالة إلا في مكان واحد ويكون هناك مصدر واحد فقط للحقيقة لحالة العنصر القابل للتجميع، من المرجّح أنّه لن يتم إنشاء أخطاء بسبب حالات غير متّسقة.
- اتساق واجهة المستخدم: تظهر جميع تعديلات الحالة على الفور في واجهة المستخدم من خلال
استخدام حوامل الحالة القابلة للتتبّع، مثل
StateFlow
أوLiveData
.
تدفق البيانات أحادي الاتجاه في Jetpack Compose
تعمل العناصر القابلة للتجميع استنادًا إلى الحالة والأحداث. على سبيل المثال، لا يتم تعديل TextField
إلا عند تعديل مَعلمة value
وعرض onValueChange
مكالمة استدعاء، وهو حدث يطلب تغيير القيمة إلى قيمة جديدة. تحدِّد دالة Compose
كائن State
كحامل قيمة، وتؤدي التغييرات في قيمة الحالة
إلى إعادة التركيب. يمكنك الاحتفاظ بالحالة في remember { mutableStateOf(value) }
أو rememberSaveable { mutableStateOf(value)
، وذلك حسب المدة التي تحتاج فيها إلى تذكُّر القيمة.
نوع قيمة العنصر القابل للتجميع TextField
هو String
، لذا يمكن أن تأتي هذه القيمة
من أي مكان، سواء من قيمة مُبرمَجة بشكلٍ ثابت أو من ViewModel أو من العنصر القابل للتجميع
الرئيسي. لست مضطرًا إلى الاحتفاظ بها في عنصر State
، ولكن عليك
تعديل القيمة عند استدعاء onValueChange
.
تحديد المَعلمات القابلة للتجميع
عند تحديد مَعلمات الحالة لعنصر قابل للتجميع، يجب مراعاة الأسئلة التالية:
- ما مدى قابلية إعادة استخدام العنصر القابل للتجميع أو مرونته؟
- كيف تؤثر مَعلمات الحالة في أداء هذا المكوّن القابل للتجميع؟
لتعزيز عملية الفصل وإعادة الاستخدام، يجب أن يحتوي كل عنصر قابل للتجميع على أقل قدر ممكن من المعلومات. على سبيل المثال، عند إنشاء عنصر قابل للتجميع لعرض عنوان مقالة إخبارية، يُفضَّل ضبط المعلومات التي يجب عرضها فقط، بدلاً من المقالة الإخبارية بأكملها:
@Composable fun Header(title: String, subtitle: String) { // Recomposes when title or subtitle have changed. } @Composable fun Header(news: News) { // Recomposes when a new instance of News is passed in. }
في بعض الأحيان، يؤدي استخدام المَعلمات الفردية أيضًا إلى تحسين الأداء. على سبيل المثال، إذا كان
News
يحتوي على معلومات أكثر من title
وsubtitle
فقط، عند تمرير
مثيل جديد من News
إلى Header(news)
، سيتم مجددًا
تجميع العنصر القابل للتجميع، حتى إذا لم تتغيّر title
وsubtitle
.
يجب مراعاة عدد المَعلمات التي يتم تمريرها بعناية. إنّ استخدام دالة تحتوي على مَعلمات كثيرة جدًا يقلل من سهولة استخدامها، لذا يُفضَّل في هذه الحالة تجميعها في فئة.
الأحداث في ميزة "الإنشاء"
يجب تمثيل كل إدخال في تطبيقك كحدث: النقرات وتغييرات النص
وحتى الموقّتات أو التعديلات الأخرى. بما أنّ هذه الأحداث تغيّر حالة واجهة المستخدم،
يجب أن يكون ViewModel
هو المسؤول عن معالجتها وتعديل حالة واجهة المستخدم.
يجب ألا تغيّر طبقة واجهة المستخدم الحالة خارج معالج الأحداث لأنّ ذلك قد يؤدي إلى حدوث تناقضات وأخطاء في تطبيقك.
يُفضَّل تمرير قيم ثابتة لواجهات دالة lambda الخاصة بالحالة ومعالج الأحداث. ينطوي هذا الأسلوب على المزايا التالية:
- تحسين إمكانية إعادة الاستخدام
- التأكّد من أنّ واجهة المستخدم لا تغيّر قيمة الحالة مباشرةً
- يمكنك تجنُّب مشاكل المعالجة المتزامنة لأنّك تتأكّد من عدم تحوُّل الحالة من سلسلة محادثات أخرى.
- وغالبًا ما يؤدي ذلك إلى تقليل تعقيد الرمز البرمجي.
على سبيل المثال، يمكن استدعاء عنصر قابل للتجميع يقبل String
ولامدا كمَعلمات
من العديد من السياقات، وهو قابل لإعادة الاستخدام بدرجة كبيرة. لنفترض أنّه يتم عرض نص دائمًا في
شريط التطبيق العلوي في تطبيقك، وأنّه يتضمّن زر الرجوع. يمكنك تحديد MyAppTopAppBar
تركيبة أكثر عمومية تتلقّى النص واسم ملف التمرير للخلف
كمَعلمات:
@Composable fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) { TopAppBar( title = { Text( text = topAppBarText, textAlign = TextAlign.Center, modifier = Modifier .fillMaxSize() .wrapContentSize(Alignment.Center) ) }, navigationIcon = { IconButton(onClick = onBackPressed) { Icon( Icons.AutoMirrored.Filled.ArrowBack, contentDescription = localizedString ) } }, // ... ) }
نماذج العرض والحالات والأحداث: مثال
باستخدام ViewModel
وmutableStateOf
، يمكنك أيضًا توفير تدفق بيانات
أحادي الاتجاه في تطبيقك في حال استيفاء أحد الشروط التالية:
- يتم عرض حالة واجهة المستخدم من خلال حوامل الحالة القابلة للتتبّع، مثل
StateFlow
أوLiveData
. - يعالج
ViewModel
الأحداث الواردة من واجهة المستخدم أو الطبقات الأخرى من تطبيقك ويحدّث حامل الحالة استنادًا إلى الأحداث.
على سبيل المثال، عند تنفيذ شاشة تسجيل الدخول، من المفترض أن يؤدي النقر على زر تسجيل الدخول إلى عرض تطبيقك لحلقة تقدم وإجراء طلب اتصال بالشبكة. إذا كان تسجيل الخطوة ناجحًا، سينتقل تطبيقك إلى شاشة مختلفة. وفي حال حدث خطأ، سيعرض التطبيق شريط معلومات سريع. في ما يلي كيفية وضع نماذج لحالة الشاشة والحدث:
تتضمّن الشاشة أربع حالات:
- تم تسجيل الخروج: عندما لم يسجّل المستخدم الدخول بعد
- جارٍ: عندما يحاول تطبيقك حاليًا تسجيل دخول المستخدم من خلال إجراء طلب بيانات من الشبكة.
- خطأ: عندما يحدث خطأ أثناء تسجيل الدخول.
- مسجّل الدخول: عندما يكون المستخدم مسجّلاً الدخول.
يمكنك وضع نماذج لهذه الحالات على أنّها فئة مختومة. يعرِض ViewModel
الحالة على أنّه
State
، ويضبط الحالة الأولية، ويحدّث الحالة حسب الحاجة. يعالج العنصر
ViewModel
أيضًا حدث تسجيل الدخول من خلال عرض طريقة onSignIn()
.
class MyViewModel : ViewModel() { private val _uiState = mutableStateOf<UiState>(UiState.SignedOut) val uiState: State<UiState> get() = _uiState // ... }
بالإضافة إلى واجهة برمجة التطبيقات mutableStateOf
، توفّر أداة Compose LiveData
وFlow
و
Observable
لتسجيلها كمستمع وتمثيل القيمة كحالة.
class MyViewModel : ViewModel() { private val _uiState = MutableLiveData<UiState>(UiState.SignedOut) val uiState: LiveData<UiState> get() = _uiState // ... } @Composable fun MyComposable(viewModel: MyViewModel) { val uiState = viewModel.uiState.observeAsState() // ... }
مزيد من المعلومات
لمزيد من المعلومات حول البنية في Jetpack Compose، يمكنك الرجوع إلى المراجع التالية:
نماذج
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- State وJetpack Compose
- حفظ حالة واجهة المستخدم في Compose
- معالجة إدخال المستخدم