في Compose، تكون واجهة المستخدم غير قابلة للتغيير، أي لا يمكن تعديلها بعد رسمها. يمكنك التحكّم في حالة واجهة المستخدم. في كل مرة تتغير فيها حالة واجهة المستخدم، يعيد 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 ودالة lambda كمعلَمات من سياقات متعددة، كما أنّه قابل لإعادة الاستخدام بشكل كبير. لنفترض أنّ شريط التطبيق العلوي في تطبيقك يعرض دائمًا نصًا ويتضمّن زر رجوع. يمكنك تحديد 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 ) } }, // ... ) }
ViewModels والحالات والأحداث: مثال
باستخدام 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 غير مفعّلة
- الحالة وJetpack Compose
- حفظ حالة واجهة المستخدم في Compose
- التعامل مع إدخالات المستخدم