في 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 ) } }, // ... ) }
نماذج العرض والحالات والأحداث: مثال
باستخدام 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
- التعامل مع بيانات أدخلها المستخدم