Jetpack Compose هي مجموعة أدوات حديثة لواجهة المستخدم التعريفية في Android. تسهِّل ميزة Compose كتابة واجهة المستخدم في تطبيقك وصيانتها من خلال توفير واجهة برمجة تطبيقات تعريفية تتيح لك عرض واجهة المستخدم في تطبيقك بدون تغيير طرق عرض الواجهة الأمامية بشكل ضروري. تحتاج هذه المصطلحات إلى بعض التفسير، ولكنّ الآثار المترتبة عليها مهمة ل تصميم تطبيقك.
منهج البرمجة التعريفية
سابقًا، كان تمثيل العرض الهرمي لنظام التشغيل Android قابلاً لتمثيل العرض التدرّجي لأدوات واجهة المستخدم. عندما تتغيّر حالة التطبيق بسبب تفاعلات المستخدِم، يجب تعديل التسلسل الهرمي لواجهة المستخدم لعرض البيانات الحالية.
إنّ الطريقة الأكثر شيوعًا لتعديل واجهة المستخدم هي التنقّل في الشجرة باستخدام دوالّ مثل
findViewById()
، وتغيير
العقد من خلال استدعاء طرق مثل button.setText(String)
أو
container.addChild(View)
أو img.setImageBitmap(Bitmap)
. تغير هذه الطرق
الحالة الداخلية للأداة.
إنّ معالجة المشاهدات يدويًا تؤدي إلى زيادة احتمال حدوث أخطاء. إذا تم عرض قطعة من البيانات في أماكن متعدّدة، من السهل نسيان تعديل أحد ملفّات العرض التي تعرضها. ومن السهل أيضًا إنشاء حالات غير قانونية عندما يتعارض تحديثان بطريقة غير متوقعة. على سبيل المثال، قد يحاول أحد التحديثات ضبط قيمة لعقدة تمت إزالتها للتو من واجهة المستخدم. بشكل عام، تزداد صعوبة صيانة البرامج مع زيادة عدد العروض التي تتطلّب التحديث.
على مدار السنوات العديدة الماضية، بدأت الصناعة بأكملها في الانتقال إلى نموذج واجهة مستخدم توضيحي، ما يسهّل إلى حد كبير عملية الهندسة المرتبطة بإنشاء واجهات المستخدم وتعديلها. تعمل هذه التقنية من خلال إعادة إنشاء الشاشة بالكامل من الصفر، ثم تطبيق التغيُّرات اللازمة فقط. ويتجنّب هذا الأسلوب تعقيد تعديل التدرّج الهرمي لعرض الذي يتضمّن حالة. Compose هو إطار عمل لتصميم واجهة المستخدم.
يتمثل أحد التحديات التي تواجه إعادة إنشاء الشاشة بأكملها في أنها قد تكون مكلفة، من حيث الوقت وقوة الحوسبة واستخدام البطارية. للحدّ من هذه التكلفة، يختار تطبيق "الكتابة الذكية" أجزاء واجهة المستخدم التي يجب redrawn إعادة رسمها في أي وقت. ويؤدّي ذلك إلى بعض النتائج في ما يتعلّق بطريقة تصميم مكونات واجهة المستخدم، كما هو موضّح في مقالة إعادة التركيب.
دالة بسيطة قابلة للإنشاء
باستخدام Compose، يمكنك إنشاء واجهة المستخدم من خلال تحديد مجموعة من الدوال
القابلة للتجميع التي تتلقّى البيانات وتُنشئ عناصر واجهة المستخدم. من الأمثلة البسيطة
تطبيق مصغّر Greeting
يتلقّى String
ويُرسِل تطبيق مصغّر Text
يعرض رسالة ترحيب.
الشكل 1: دالة قابلة للتجميع بسيطة يتم تمريرها بالبيانات واستخدامها لمحاولة عرض تطبيق مصغّر نصي على الشاشة
إليك بعض الأشياء الجديرة بالملاحظة حول هذه الدالة:
تمّت إضافة تعليق توضيحي
@Composable
إلى الدالة. يجب أن تحتوي جميع الدوال المكوّنة على هذا التعليق التوضيحي، حيث يُعلم هذا التعليق التوضيحي مُجمِّع Compose بأنّ هذه الدالة مخصّصة لتحويل البيانات إلى واجهة مستخدم.تأخذ الدالة البيانات. يمكن للدوال القابلة للتعديل أن تقبل المعلمات، التي تسمح لمنطق التطبيق بوصف واجهة المستخدم. في هذه الحالة، يقبل التطبيق المصغّر
String
لكي يتمكّن من تحية المستخدم باسمه.تعرِض الدالة النص في واجهة المستخدم. ويتم ذلك عن طريق استدعاء الدالة
Text()
القابلة للإنشاء، التي تنشئ في الواقع عنصر واجهة المستخدم النصية. تُنشئ الدوال المركّبة هيكل واجهة المستخدم من خلال استدعاء دوال مركّبة أخرى.لا تُرجع الدالة أيّ قيمة. لا تحتاج وظائف الإنشاء التي تُنشئ واجهة المستخدم إلى عرض أي شيء، لأنّها تصف حالة الشاشة المطلوبة بدلاً من إنشاء التطبيقات المصغّرة لواجهة المستخدم.
هذه الدالة سريعة، متساوية، وليست لها تأثيرات جانبية.
- وتعمل الدالة بالطريقة نفسها عند استدعائها عدة مرات باستخدام الوسيطة نفسها، ولا تستخدم قيمًا أخرى مثل المتغيّرات العمومية أو عمليات استدعاء الدالة
random()
. - تصف الدالة واجهة المستخدم بدون أي آثار جانبية، مثل تعديل الخصائص أو المتغيرات العمومية.
وبشكل عام، يجب كتابة جميع الدوال القابلة للإنشاء باستخدام هذه السمات، وذلك للأسباب التي تمت مناقشتها في مقالة إعادة الإنشاء.
- وتعمل الدالة بالطريقة نفسها عند استدعائها عدة مرات باستخدام الوسيطة نفسها، ولا تستخدم قيمًا أخرى مثل المتغيّرات العمومية أو عمليات استدعاء الدالة
تغيير المنهج التعريفي
مع العديد من مجموعات الأدوات الضرورية لواجهة المستخدم المهيأة للكائنات، يمكنك تهيئة واجهة المستخدم من خلال إنشاء شجرة من الأدوات. يمكنك تنفيذ ذلك غالبًا عن طريق تضخيم ملف تنسيق XML. تحافظ كل أداة على حالتها الداخلية، وتوفّر طُرق الحصول على البيانات وتعديلها التي تسمح لمنطق التطبيق بالتفاعل مع الأداة.
في النهج التعريفي لـ Compose، تكون التطبيقات المصغّرة غير مرتبطة بحالة نسبيًا ولا تُعرِض
وظائف ضبط أو الحصول. في الواقع، لا يتم عرض التطبيقات المصغّرة كعناصر.
يمكنك تحديث واجهة المستخدم عن طريق استدعاء نفس الدالة القابلة للإنشاء بوسيطات مختلفة. يسهّل ذلك تقديم حالة للأنماط المعمارية، مثل
ViewModel
، كما هو موضّح في دليل بنية التطبيق. بعد ذلك، تصبح العناصر القابلة للتجميع
مسؤولة عن تحويل حالة التطبيق الحالية إلى واجهة مستخدم في كل مرة تتم فيها تعديل البيانات القابلة للتتبّع.
الشكل 2. يقدّم منطق التطبيق البيانات إلى الدالة القابلة للتجميع من المستوى الأعلى. وتستخدم هذه الدالة البيانات لوصف واجهة المستخدم من خلال استدعاء عناصر قابلة للتجميع أخرى، و تمرِّر البيانات المناسبة إلى هذه العناصر القابلة للتجميع، ثم إلى أسفل التسلسل الهرمي.
عندما يتفاعل المستخدم مع واجهة المستخدم، تُنشئ واجهة المستخدم أحداثًا مثل onClick
.
يجب أن ترسل هذه الأحداث إشعارًا إلى منطق التطبيق، ما قد يؤدي إلى تغيير حالة التطبيق بعد ذلك.
عندما تتغير الحالة، يتم استدعاء الدوال القابلة للإنشاء مرة أخرى بالبيانات الجديدة. يؤدي ذلك إلى إعادة رسم عناصر واجهة المستخدم، وتُعرف هذه العملية باسم
إعادة التركيب.
الشكل 3: تفاعل المستخدم مع عنصر في واجهة المستخدم، ما أدّى إلى بدء حدث. يستجيب منطق التطبيق للحدث، ثم يتم استدعاء الدوالّ القابلة للتجميع مجددًا تلقائيًا باستخدام مَعلمات جديدة، إذا لزم الأمر.
محتوى ديناميكي
بما أنّ الدوال القابلة للإنشاء مكتوبة بلغة Kotlin بدلاً من XML، يمكن أن تكون ديناميكية مثل أي رمز Kotlin آخر. على سبيل المثال، لنفترض أنّك تريد إنشاء واجهة مستخدم ترحب بقائمة من المستخدمين:
@Composable fun Greeting(names: List<String>) { for (name in names) { Text("Hello $name") } }
تستقبل هذه الدالة قائمة بالأسماء وتُنشئ رسالة ترحيب لكل مستخدم.
يمكن أن تكون الدوالّ القابلة للتجميع معقّدة جدًا. يمكنك استخدام عبارات if
لتحديد ما إذا كنت تريد عرض عنصر واجهة مستخدم معيّن. يمكنك استخدام الحلقات. يمكنك
استدعاء الدوال المساعِدة. يمكنك الاستفادة من المرونة الكاملة للغة الأساسية.
تُعدّ هذه الإمكانات والمرونة من المزايا الرئيسية لواجهة Jetpack Compose.
إعادة التركيب
في نموذج واجهة مستخدم ضروري، لتغيير تطبيق مصغّر، يمكنك استدعاء دالة setter على الأداة لتغيير حالتها الداخلية. في Compose، يمكنك استدعاء الدالة القابلة للتركيب مرة أخرى مع بيانات جديدة. يؤدي إجراء ذلك إلى إعادة إنشاء الدالة، إذ تتم إعادة رسم الأدوات المنبعثة من الدالة إذا لزم الأمر باستخدام بيانات جديدة. يمكن لإطار عمل Compose إعادة إنشاء المكونات التي تغيّرت فقط بذكاء.
على سبيل المثال، ضع في اعتبارك هذه الدالة القابلة للإنشاء التي تعرض زرًا:
@Composable fun ClickCounter(clicks: Int, onClick: () -> Unit) { Button(onClick = onClick) { Text("I've been clicked $clicks times") } }
في كل مرة يتم فيها النقر على الزر، يعدِّل المتصل قيمة clicks
.
يستدعي Compose دالة lambda باستخدام الدالة Text
مرة أخرى لعرض القيمة الجديدة، وتُسمى هذه العملية إعادة التركيب. لا تتم إعادة إنشاء الدوال الأخرى التي لا تعتمد
على القيمة.
كما ناقشنا، يمكن أن تكون إعادة تكوين شجرة واجهة المستخدم بالكامل عملية حسابية مكلفة، ما يؤدي إلى استخدام طاقة الحوسبة وعمر البطارية. يحلّ تطبيق "الإنشاء" هذه المشكلة من خلال ميزة إعادة التركيب الذكية.
إعادة التركيب هي عملية استدعاء الدوالّ القابلة للتجميع مرة أخرى عند تغيُّر المدخلات. يحدث هذا عندما تتغير مدخلات الدالة. عندما تتم إعادة إنشاء الإنشاء استنادًا إلى إدخالات جديدة، فإنه يستدعي فقط الدوال أو دالة lambdas التي ربما تغيّرت، وتتخطى الباقي. من خلال تخطّي جميع الدوالّ أو الدوالّ اللامدا التي لم يتم تغيير مَعلماتها، يمكن لـ Compose إعادة الإنشاء بكفاءة.
لا تعتمد أبدًا على التأثيرات الجانبية الناتجة عن تنفيذ الدوالّ القابلة للتجميع، لأنّه قد يتم تخطّي إعادة تركيب الدوالّ. وفي حال إجراء ذلك، قد يواجه المستخدمون سلوكًا غريبًا وغير متوقّع في تطبيقك. ويُعدّ أي تغيير مرئيًا لبقية أجزاء تطبيقك من الآثار الجانبية. على سبيل المثال، جميع الإجراءات التالية هي آثار جانبية خطيرة:
- الكتابة في سمة عنصر مشترَك
- تعديل سمة قابلة للرصد في
ViewModel
- تعديل الإعدادات المفضّلة المشتركة
قد تتم إعادة تنفيذ الدوال القابلة للتجميع في كل إطار، مثل عند عرض صورة متحركة. يجب أن تكون الدوالّ القابلة للتجميع سريعة لتجنُّب البطء أثناء عرض الصور المتحركة. إذا كنت بحاجة إلى تنفيذ عمليات مُكلّفة، مثل القراءة من الإعدادات المفضّلة المشتركة، يمكنك تنفيذها في دالة معالجة متزامنة في الخلفية وتمرير نتيجة قيمة إلى الدالة القابلة للتجميع كمَعلمة.
على سبيل المثال، تنشئ هذه التعليمة البرمجية عنصرًا قابلاً للتجميع لتعديل قيمة في
SharedPreferences
. يجب ألا تتم قراءة المحتوى القابل للإنشاء أو كتابته من خلال الإعدادات المفضّلة المشتركة نفسها. بدلاً من ذلك، تنقل هذه التعليمة البرمجية القراءة والكتابة إلى ViewModel
في دالة معالجة متزامنة في الخلفية. يُرسِل منطق التطبيق القيمة الحالية باستخدام دالّة callback لبدء عملية التحديث.
@Composable fun SharedPrefsToggle( text: String, value: Boolean, onValueChanged: (Boolean) -> Unit ) { Row { Text(text) Checkbox(checked = value, onCheckedChange = onValueChanged) } }
يتناول هذا المستند عددًا من الأمور التي يجب أخذها في الاعتبار عند استخدام ميزة "الإنشاء":
- تتخطّى إعادة التركيب أكبر عدد ممكن من الدوالّ القابلة للتجميع ودوالّ LAMBDA.
- إنّ إعادة التركيب متفائلة وقد يتم إلغاؤها.
- يمكن تشغيل الدالة القابلة للإنشاء بشكل متكرر، مثل كل إطار من الرسوم المتحركة.
- يمكن تنفيذ الدوالّ القابلة للتجميع بالتوازي.
- يمكن تنفيذ الدوالّ القابلة للتجميع بأي ترتيب.
ستتناول الأقسام التالية كيفية إنشاء دوال قابلة للتجميع لدعم إعادة التركيب. وفي كل الحالات، فإنّ أفضل ممارسة هي إبقاء الدوال القابلة للإنشاء سريعة ومنتظمة وخالية من الآثار الجانبية.
يتم تخطّي إعادة التركيب قدر الإمكان
عندما تكون أجزاء من واجهة المستخدم غير صالحة، تبذل ميزة "الإنشاء" قصارى جهدها لإعادة إنشاء الأجزاء التي تحتاج إلى تعديل فقط. وهذا يعني أنّه قد يتخطّى التطبيق إعادة تشغيل زر واحد قابل للإنشاء بدون تنفيذ أي عنصر من العناصر القابلة للإنشاء فوقه أو أسفله في شجرة واجهة المستخدم.
يمكن إعادة إنشاء كل دالة قابلة للإنشاء ودالة lambda من تلقاء نفسها. وفيما يلي مثال يوضح كيف يمكن لإعادة التركيب أن تتخطى بعض العناصر عند عرض قائمة:
/** * Display a list of names the user can click with a header */ @Composable fun NamePicker( header: String, names: List<String>, onNameClicked: (String) -> Unit ) { Column { // this will recompose when [header] changes, but not when [names] changes Text(header, style = MaterialTheme.typography.bodyLarge) HorizontalDivider() // LazyColumn is the Compose version of a RecyclerView. // The lambda passed to items() is similar to a RecyclerView.ViewHolder. LazyColumn { items(names) { name -> // When an item's [name] updates, the adapter for that item // will recompose. This will not recompose when [header] changes NamePickerItem(name, onNameClicked) } } } } /** * Display a single name the user can click. */ @Composable private fun NamePickerItem(name: String, onClicked: (String) -> Unit) { Text(name, Modifier.clickable(onClick = { onClicked(name) })) }
قد يكون كل نطاق من هذه النطاقات هو الإجراء الوحيد الذي يتم تنفيذه أثناء إعادة التركيب.
قد يتخطّى التركيب دالة lambda Column
بدون تنفيذ أيّ من الدوالّ الرئيسية
عند تغيير header
. وعند تنفيذ Column
، قد يختار Compose
تخطّي عناصر LazyColumn
إذا لم يتغيّر names
.
مرة أخرى، يجب أن يكون تنفيذ جميع الدوالّ القابلة للتجميع أو دوالّ lambda خاليًا من الآثار الجانبية. عندما تحتاج إلى تنفيذ تأثير جانبي، يمكنك تشغيله من خلال طلب إعادة الاتصال.
إعادة التركيب متشائمة
تبدأ عملية إعادة التركيب عندما يظنّ تطبيق Compose أنّه قد تغيّرت مَعلمات العنصر القابل للتركيب. تُعدّ عملية إعادة الإنشاء أمرًا متفائل، ما يعني أنّ ميزة Compose تتوقع إنهاء عملية إعادة الإنشاء قبل أن تتغير المعلمات مرة أخرى. إذاتغيّرت إحدى المَعلمات قبل انتهاء عملية إعادة التركيب، قد تلغي ميزة "الإنشاء" عملية إعادة التركيب وتعيد تشغيلها باستخدام المَعلمة الجديدة.
عند إلغاء إعادة التركيب، تتخلّص أداة الإنشاء من شجرة واجهة المستخدم من إعادة التركيب. إذا كانت لديك أيّ تأثيرات جانبية تعتمد على واجهة المستخدم التي يتم عرضها، سيتم تطبيق التأثير الجانبي حتى إذا تم إلغاء عملية الإنشاء. وقد يؤدي ذلك إلى عدم اتساق حالة التطبيق.
تأكَّد من أنّ جميع الدوالّ القابلة للتجميع ودوالّ Lambda لا تؤدي إلى تكرار الإجراء ولا تتسبّب في أي آثار جانبية لمعالجة إعادة التركيب التفاؤلي.
قد يتم تشغيل الدوالّ القابلة للتجميع بشكلٍ متكرّر.
في بعض الحالات، قد يتم تشغيل دالة قابلة للتجميع لكل إطار من رسوم متحركة لواجهة المستخدم. إذا كانت الدالة تُجري عمليات مُكلّفة، مثل القراءة من ملف تخزين الجهاز، يمكن أن تتسبّب الدالة في حدوث تقطُّع في واجهة المستخدم.
على سبيل المثال، إذا حاولت الأداة قراءة إعدادات الجهاز، من المحتمل أن تتم قراءة هذه الإعدادات مئات المرات في الثانية، ما قد يؤدي إلى تأثيرات كارثية في أداء تطبيقك.
إذا كانت الدالة القابلة للتجميع تحتاج إلى بيانات، يجب أن تحدِّد مَعلمات لتلك
البيانات. يمكنك بعد ذلك نقل العمل المكثّف إلى سلسلة محادثات أخرى خارج عملية الcomposing، ونقل البيانات إلى Compose باستخدام mutableStateOf
أو LiveData
.
يمكن تشغيل الدوالّ القابلة للتجميع بالتوازي
يمكن أن تحسِّن أداة Compose عملية إعادة التركيب من خلال تشغيل الدوالّ القابلة للتجميع في الموازاة. سيسمح ذلك لتطبيق Compose بالاستفادة من النوى المتعددة وتنفيذ الدوال القابلة للتجميع التي لا تظهر على الشاشة بأولوية أقل.
سيؤدي هذا التحسين إلى تنفيذ دالة قابلة للتجميع ضمن مجموعة
من سلاسل المهام التي تعمل في الخلفية.
إذا كانت دالة مركّبة تستدعي دالة في ViewModel
،
قد تستدعي دالة Compose تلك الدالة من عدة سلاسل محادثات في الوقت نفسه.
لضمان عمل التطبيق بشكل صحيح، يجب ألا يكون لجميع الدوال القابلة
للإنشاء أي آثار جانبية. بدلاً من ذلك، يمكنك بدء التأثيرات الجانبية من طلبات الاستدعاء، مثل
onClick
التي يتم تنفيذها دائمًا في سلسلة مهام واجهة المستخدم.
عند استدعاء دالة قابلة للإنشاء، قد يحدث الاستدعاء في سلسلة محادثات مختلفة عن المتصل. وهذا يعني أنّه يجب تجنُّب الرمز البرمجي الذي يعدّل المتغيّرات في دالة lambda قابلة للتركيب، وذلك لأنّ هذا الرمز البرمجي غير آمن لتعدد المواضيع، ولأنّه يشكّل أثرًا جانبيًا غير مسموح به لدالة lambda القابلة للتركيب.
في ما يلي مثال يعرض عنصرًا قابلاً للتجميع يعرض قائمة وعدد عناصرها:
@Composable fun ListComposable(myList: List<String>) { Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Text("Item: $item") } } Text("Count: ${myList.size}") } }
هذا الرمز البرمجي خالٍ من الآثار الجانبية، ويحوّل قائمة الإدخال إلى واجهة مستخدم. هذا رمز برمجي رائع لعرض قائمة صغيرة. ومع ذلك، إذا كانت الدالة تكتب في متتغيّر محلي، لن يكون هذا الرمز آمنًا أو صحيحًا في مؤشرات الترابط:
@Composable fun ListWithBug(myList: List<String>) { var items = 0 Row(horizontalArrangement = Arrangement.SpaceBetween) { Column { for (item in myList) { Card { Text("Item: $item") items++ // Avoid! Side-effect of the column recomposing. } } } Text("Count: $items") } }
في هذا المثال، يتم تعديل items
مع كل إعادة تركيب. ويمكن أن يكون ذلك عند كل لقطة من لقطات الصورة المتحركة أو عند تعديل القائمة. وفي كلتا الحالتَين، ستعرض واجهة المستخدم عددًا غير صحيح. لهذا السبب، لا تتوفّر عمليات الكتابة هذه في
Compose. ومن خلال حظر عمليات الكتابة هذه، نسمح للإطار الأساسي بتغيير المواضيع
لتنفيذ الدوالّ lambda القابلة للتركيب.
يمكن تنفيذ الدوالّ القابلة للتجميع بأي ترتيب.
إذا اطّلعت على رمز دالة قابلة للتجميع، قد تفترض أنّه يتم تنفيذ الرمز بالترتيب الذي يظهر به. ولا يمكن ضمان صحة هذه المعلومات. إذا كانت دالة قابلة للتجميع تحتوي على طلبات استدعاء لدوالّ قابلة للتجميع أخرى، قد يتم تنفيذ تلك الدوال بأي ترتيب. تتيح ميزة "الإنشاء" إمكانية التعرّف على أنّه للبعض عناصر واجهة المستخدم أولوية أعلى من غيرها، وبالتالي رسمها أولاً.
على سبيل المثال، لنفترض أنّ لديك رمزًا برمجيًا مثل هذا لرسم ثلاث شاشات في تنسيق علامة تبويب:
@Composable fun ButtonRow() { MyFancyNavigation { StartScreen() MiddleScreen() EndScreen() } }
قد يتم إجراء المكالمات إلى StartScreen
وMiddleScreen
وEndScreen
بأي ترتيب. ويعني ذلك أنّه لا يمكنك مثلاً ضبط StartScreen()
على ضبط متغيّر عام (تأثير جانبي) والسماح لـ MiddleScreen()
بالاستفادة من هذا التغيير. بدلاً من ذلك، يجب أن تكون كلّ من هذه الدوالّ مكتفية ذاتيًا.
مزيد من المعلومات
لمعرفة المزيد حول كيفية التفكير في Compose والدوالّ القابلة للتجميع، اطّلِع على الموارد الإضافية التالية.
الفيديوهات
ما من اقتراحات في الوقت الحالي.
يُرجى محاولة تسجيل الدخول إلى حسابك على Google.