في Compose، تكون واجهة المستخدم غير قابلة للتغيير، ولا يمكن تعديلها بعد رسمها. ويمكنك التحكّم في حالة واجهة المستخدم. في كل مرة تتغيّر فيها حالة واجهة المستخدم
، يعيد Compose إنشاء أجزاء شجرة واجهة المستخدم التي تغيّرت. يمكن أن تقبل الدوال القابلة للإنشاء الحالة وتعرض الأحداث، على سبيل المثال، يقبل TextField قيمة ويعرض معاودة الاتصال onValueChange التي تطلب من معالج رد الاتصال تغيير القيمة.
var name by remember { mutableStateOf("") } OutlinedTextField( value = name, onValueChange = { name = it }, label = { Text("Name") } )
بما أنّ الدوال القابلة للإنشاء تقبل الحالة وتعرض الأحداث، يتناسب نمط تدفق البيانات أحادي الاتجاه بشكلٍ جيد مع Jetpack Compose. يركّز هذا الدليل على كيفية تنفيذ نمط تدفق البيانات أحادي الاتجاه في Compose، وكيفية تنفيذ الأحداث وحاملي الحالة، وكيفية استخدام نماذج العرض في 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.
- ينشئ
mutableStateOf(value)عنصرMutableState، وهو نوع قابل للمراقبة في Compose. تؤدي أي تغييرات في قيمته إلى جدولة إعادة التكوين لأي دوال مركّبة تقرأ هذه القيمة. - يخزّن
rememberالكائنات في التكوين، وينسى الكائن عند إزالة الدالة المركّبة التي استدعتrememberمن التكوين. - يحتفظ
rememberSaveableبالحالة أثناء تغييرات الإعدادات من خلال حفظها فيBundle.
تحديد مَعلمات الدوال القابلة للإنشاء
عند تحديد مَعلمات الحالة لدالة قابلة للإنشاء، ضَع في اعتبارك الأسئلة التالية:
- ما مدى سهولة إعادة استخدام الدالة المركّبة أو مرونتها؟
- كيف تؤثر مَعلمات الحالة في أداء هذه الدالة القابلة للإنشاء؟
لتعزيز الفصل وسهولة إعادة الاستخدام، يجب أن تحتوي كل دالة مركّبة على أقل قدر ممكن من المعلومات. على سبيل المثال، عند إنشاء دالة مركّبة لعرض رأس مقالة إخبارية، من الأفضل تمرير المعلومات التي يجب عرضها فقط بدلاً من المقالة الإخبارية بأكملها:
@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.
ضَع في اعتبارك بعناية عدد المَعلمات التي تمرّرها. يؤدي وجود دالة تحتوي على عدد كبير جدًا من المَعلمات إلى تقليل سهولة استخدام الدالة، لذا من الأفضل في هذه الحالة تجميعها في فئة.
الأحداث في Compose
يجب تمثيل كل إدخال في تطبيقك كحدث: النقرات والتغييرات في النص وحتى المؤقتات أو التعديلات الأخرى. بما أنّ هذه الأحداث تغيّر حالة واجهة المستخدم، يجب أن يعالجها 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 ) } }, // ... ) }
نماذج العرض والحالات والأحداث: مثال
باستخدام 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
- معالجة بيانات أدخلها المستخدم