Jetpack Compose هي مجموعة أدوات حديثة تعريفية لواجهة المستخدم على Android. تسهّل Compose كتابة واجهة مستخدم تطبيقك وصيانتها من خلال توفير واجهة برمجة تطبيقات تعريفية تتيح لك عرض واجهة مستخدم تطبيقك بدون تعديل طرق العرض في الواجهة الأمامية بشكل إلزامي. تحتاج هذه المصطلحات إلى بعض التوضيح، ولكن آثارها مهمة لتصميم تطبيقك.
نموذج البرمجة التعريفية
في السابق، كان من الممكن تمثيل تسلسل هرمي لعناصر العرض في Android على شكل شجرة لعناصر واجهة المستخدم. عندما تتغير حالة التطبيق بسبب تفاعلات المستخدمين مثلاً، يجب تعديل التسلسل الهرمي لواجهة المستخدم لعرض البيانات الحالية.
الطريقة الأكثر شيوعًا لتعديل واجهة المستخدم هي الانتقال إلى الشجرة باستخدام دوال مثل findViewById()
وتغيير العُقد من خلال استدعاء طرق مثل button.setText(String)
أو container.addChild(View)
أو img.setImageBitmap(Bitmap)
. تغيّر هذه الطرق الحالة الداخلية للأداة.
تزيد معالجة المشاهدات يدويًا من احتمال حدوث أخطاء. إذا تم عرض جزء من البيانات في مواضع متعددة، قد تنسى تعديل إحدى طرق العرض التي تعرضه. ويمكن أن يؤدي ذلك أيضًا إلى حالات غير قانونية، عندما يتعارض تحديثان بطريقة غير متوقّعة. على سبيل المثال، قد تحاول عملية تعديل ضبط قيمة لعنصر تمّت إزالته للتو من واجهة المستخدم. بشكل عام، تزداد صعوبة صيانة البرنامج مع زيادة عدد طرق العرض التي تتطلّب التعديل.
على مدار السنوات العديدة الماضية، بدأ المجال بأكمله في التحوّل إلى نموذج واجهة مستخدم تعريفية. يبسّط هذا النموذج عملية التصميم المرتبطة بإنشاء واجهات المستخدمين وتعديلها. وتعمل هذه التقنية من خلال إعادة إنشاء الشاشة بأكملها من البداية، ثم تطبيق التغييرات اللازمة فقط. يتجنّب هذا الأسلوب تعقيد عملية تعديل التدرّج الهرمي لعرض ذي حالة يدويًا. Compose هو إطار عمل تعريفي لواجهة المستخدم.
أحد التحديات التي تواجه إعادة إنشاء الشاشة بأكملها هو أنّها قد تكون مكلفة من حيث الوقت وقوة الحوسبة واستهلاك البطارية. وللحدّ من هذه التكلفة، يختار Compose بذكاء الأجزاء التي يجب إعادة رسمها من واجهة المستخدم في أي وقت. ويترتب على ذلك بعض الآثار على طريقة تصميم مكونات واجهة المستخدم، كما هو موضّح في إعادة التركيب.
مثال على دالة قابلة للإنشاء
باستخدام Compose، يمكنك إنشاء واجهة المستخدم من خلال تحديد مجموعة من الدوال
القابلة للإنشاء التي تتلقّى البيانات وتعرض عناصر واجهة المستخدم. أحد الأمثلة على ذلك هو Greeting
أداة تأخذ String
وتعرض Text
أداة تعرض رسالة ترحيب.

في ما يلي بعض الملاحظات الجديرة بالذكر حول هذه الدالة:
- التعليق التوضيحي: يتم إضافة التعليق التوضيحي
@Composable
إلى الدالة. يجب أن تتضمّن جميع الدوال القابلة للإنشاء هذه التعليق التوضيحي. تُعلم هذه التعليق التوضيحي مترجم Compose بأنّ الغرض من هذه الدالة هو تحويل البيانات إلى واجهة مستخدم. - إدخال البيانات: تستقبل الدالة البيانات. يمكن أن تقبل الدوال البرمجية القابلة للإنشاء
مَعلمات تتيح لمنطق التطبيق وصف واجهة المستخدم. في هذه الحالة، يقبل التطبيق المصغّر
String
حتى يتمكّن من تحية المستخدم بالاسم. - عرض واجهة المستخدم: تعرض الدالة نصًا في واجهة المستخدم. ويتم ذلك من خلال استدعاء الدالة القابلة للإنشاء
Text()
التي تنشئ في الواقع عنصر واجهة المستخدم النصي. تُصدر الدوال القابلة للإنشاء هيكلية واجهة المستخدم من خلال استدعاء دوال أخرى قابلة للإنشاء. - لا يتم عرض أي قيمة: لا تعرض الدالة أي قيمة. لا تحتاج دوال Compose التي تعرض واجهة مستخدم إلى عرض أي قيمة، لأنّها تصف حالة الشاشة المستهدَفة بدلاً من إنشاء عناصر واجهة المستخدم.
السمات: هذه الدالة سريعة ومتكررة ولا تتضمّن أي آثار جانبية.
- تتصرّف الدالة بالطريقة نفسها عند استدعائها عدة مرات باستخدام الوسيطة نفسها، ولا تستخدم قيمًا أخرى، مثل المتغيرات العامة أو عمليات الاستدعاء إلى
random()
. - تصف الدالة واجهة المستخدم بدون أي آثار جانبية، مثل تعديل الخصائص أو المتغيرات العامة.
بشكل عام، يجب كتابة جميع الدوال القابلة للإنشاء باستخدام هذه الخصائص، وذلك للأسباب التي تمت مناقشتها في إعادة التركيب.
- تتصرّف الدالة بالطريقة نفسها عند استدعائها عدة مرات باستخدام الوسيطة نفسها، ولا تستخدم قيمًا أخرى، مثل المتغيرات العامة أو عمليات الاستدعاء إلى
التغيير الجذري في النموذج التصريحي
في العديد من مجموعات أدوات واجهة المستخدم الإلزامية الموجهة للكائنات، يمكنك تهيئة واجهة المستخدم من خلال إنشاء مثيل لشجرة من الأدوات. ويتم ذلك غالبًا من خلال توسيع ملف تنسيق XML. تحتفظ كل أداة بحالتها الداخلية، وتوفّر طريقتَي getter وsetter تتيحان لمنطق التطبيق التفاعل مع الأداة.
في أسلوب التصريح في Compose، تكون التطبيقات المصغّرة غير مرتبطة بحالة بشكل نسبي ولا تعرض دوال setter أو getter. في الواقع، لا يتم عرض التطبيقات المصغّرة كعناصر.
يمكنك تعديل واجهة المستخدم من خلال استدعاء الدالة البرمجية القابلة للإنشاء نفسها مع وسيطات مختلفة. ويسهّل ذلك توفير الحالة لأنماط التصميم، مثل ViewModel
، كما هو موضّح في دليل تصميم التطبيقات. بعد ذلك، تكون العناصر القابلة للإنشاء مسؤولة عن تحويل حالة التطبيق الحالية إلى واجهة مستخدم في كل مرة يتم فيها تعديل البيانات القابلة للملاحظة.

عندما يتفاعل المستخدم مع واجهة المستخدم، تنشئ واجهة المستخدم أحداثًا مثل onClick
.
يجب أن تُعلم هذه الأحداث منطق التطبيق، الذي يمكنه بعد ذلك تغيير حالة التطبيق.
عندما تتغير الحالة، يتم استدعاء الدوال القابلة للإنشاء مرة أخرى باستخدام البيانات الجديدة. ويؤدي ذلك إلى إعادة رسم عناصر واجهة المستخدم، وتُعرف هذه العملية باسم إعادة التركيب.

المحتوى الديناميكي
بما أنّ الدوال القابلة للتركيب مكتوبة بلغة Kotlin بدلاً من XML، يمكن أن تكون ديناميكية مثل أي رمز Kotlin آخر. على سبيل المثال، لنفترض أنّك تريد إنشاء واجهة مستخدم ترحّب بقائمة من المستخدمين:
@Composable fun Greeting(names: List<String>) { for (name in names) { Text("Hello $name") } }
تتلقّى هذه الدالة قائمة بالأسماء وتنشئ تحية لكل مستخدم.
يمكن أن تكون الدوال القابلة للإنشاء معقّدة جدًا. يمكنك استخدام عبارات if
لتحديد ما إذا كنت تريد عرض عنصر معيّن من عناصر واجهة المستخدم. يمكنك استخدام التكرارات الحلقية. يمكنك استدعاء الدوال المساعدة. يمكنك الاستفادة من المرونة الكاملة للغة الأساسية.
تُعدّ هذه القوة والمرونة من المزايا الرئيسية في Jetpack Compose.
إعادة التركيب
في نموذج واجهة المستخدم الإجرائي، لتغيير أداة، عليك استدعاء دالة ضبط على الأداة لتغيير حالتها الداخلية. في Compose، يمكنك استدعاء الدالة القابلة للإنشاء مرة أخرى باستخدام بيانات جديدة. يؤدي ذلك إلى إعادة إنشاء الدالة، أي إعادة رسم التطبيقات المصغّرة التي تعرضها الدالة، إذا لزم الأمر، باستخدام بيانات جديدة. يمكن لإطار عمل Compose إعادة إنشاء المكوّنات التي تم تغييرها فقط بشكل ذكي.
على سبيل المثال، ضع في اعتبارك هذه الدالة القابلة للإنشاء التي تعرض زرًا:
@Composable fun ClickCounter(clicks: Int, onClick: () -> Unit) { Button(onClick = onClick) { Text("I've been clicked $clicks times") } }
في كل مرة يتم فيها النقر على الزر، يعدّل المتصل قيمة clicks
.
تستدعي Compose دالة lambda باستخدام الدالة Text
مرة أخرى لعرض القيمة الجديدة، وتُعرف هذه العملية باسم إعادة التركيب. ولا تتم إعادة إنشاء الدوال الأخرى التي لا تعتمد على القيمة.
كما ذكرنا، يمكن أن تكون إعادة إنشاء شجرة واجهة المستخدم بأكملها مكلفة من الناحية الحسابية، ما يؤدي إلى استهلاك طاقة المعالجة وعمر البطارية. يحلّ Compose هذه المشكلة من خلال إعادة التركيب الذكي.
إعادة الإنشاء هي عملية استدعاء الدوال المركّبة مرة أخرى عند تغيُّر المَعلمات المُدخَلة. عندما تعيد Compose إنشاء التخطيط استنادًا إلى مدخلات جديدة، فإنّها تستدعي فقط الدوال أو تعبيرات lambda التي ربما تغيّرت، وتتخطى الباقي. من خلال تخطّي الدوال أو تعابير lambda التي تتضمّن مَعلمات لم تتغيّر، يعيد Compose إنشاء التركيب بكفاءة.
لا تعتمد أبدًا على الآثار الجانبية الناتجة عن تنفيذ الدوال القابلة للإنشاء، لأنّه قد يتم تخطّي إعادة إنشاء الدالة. وفي حال حدوث ذلك، قد يواجه المستخدمون سلوكًا غريبًا وغير متوقّع في تطبيقك. والتأثير الجانبي هو أي تغيير يظهر لبقية تطبيقك. على سبيل المثال، هذه الإجراءات كلها آثار جانبية خطيرة:
- الكتابة إلى خاصية كائن مشترك
- تعديل عنصر يمكن رصده في
ViewModel
- جارٍ تعديل الإعدادات المفضّلة المشترَكة
قد تتم إعادة تنفيذ الدوال البرمجية القابلة للإنشاء بشكل متكرّر، مثلاً كل إطار، كما هو الحال عند عرض رسم متحرك. يجب أن تكون الدوال القابلة للإنشاء سريعة لتجنُّب حدوث تشوّش أثناء عرض الصور المتحركة. إذا كنت بحاجة إلى تنفيذ عمليات مكلفة، مثل القراءة من الإعدادات المفضّلة المشترَكة، نفِّذها في روتين فرعي يعمل في الخلفية ومرِّر قيمة النتيجة إلى الدالة القابلة للإنشاء كمَعلمة.
على سبيل المثال، ينشئ هذا الرمز عنصرًا قابلاً للإنشاء لتعديل قيمة في
SharedPreferences
. يجب ألا يقرأ العنصر القابل للإنشاء أو يكتب من الإعدادات المفضّلة المشتركة. بدلاً من ذلك، ينقل هذا الرمز عمليات القراءة والكتابة إلى ViewModel
في روتين فرعي يعمل في الخلفية. تُمرِّر منطق التطبيق القيمة الحالية مع
دالة رد الاتصال لتفعيل عملية التحديث.
@Composable fun SharedPrefsToggle( text: String, value: Boolean, onValueChanged: (Boolean) -> Unit ) { Row { Text(text) Checkbox(checked = value, onCheckedChange = onValueChanged) } }
يناقش هذا المستند عددًا من الأمور التي يجب الانتباه إليها عند استخدام ميزة "إنشاء":
- تتخطى إعادة التركيب أكبر عدد ممكن من الدوال المركّبة وعبارات lambda.
- إعادة التكوين هي عملية متفائلة وقد يتم إلغاؤها.
- قد يتم تشغيل دالة قابلة للإنشاء بشكل متكرّر، أي كل إطار من الرسوم المتحركة.
- يمكن تنفيذ الدوال القابلة للإنشاء بالتوازي.
- يمكن تنفيذ الدوال البرمجية القابلة للإنشاء بأي ترتيب.
ستتناول الأقسام التالية كيفية إنشاء دوال برمجية قابلة للإنشاء لدعم إعادة التركيب. في كل الحالات، أفضل ممارسة هي الحفاظ على سرعة وظائفك القابلة للإنشاء، وأن تكون متكررة، وخالية من الآثار الجانبية.
تتخطى عملية إعادة التكوين أكبر قدر ممكن من الأجزاء
عندما تكون أجزاء من واجهة المستخدم غير صالحة، يبذل Compose قصارى جهده لإعادة إنشاء الأجزاء التي تحتاج إلى تعديل فقط. وهذا يعني أنّه قد يتم تخطّي إعادة تشغيل عنصر Button
القابل للإنشاء بدون تنفيذ أي من العناصر القابلة للإنشاء الأعلى أو الأدنى في شجرة واجهة المستخدم.
قد تتم إعادة إنشاء كل دالة قابلة للإنشاء و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) })) }
وقد يكون كل نطاق من هذه النطاقات هو الشيء الوحيد الذي سيتم تنفيذه أثناء إعادة التركيب.
قد تنتقل Compose إلى دالة lambda Column
بدون تنفيذ أي من العناصر الرئيسية لها عند تغيير header
. وعند تنفيذ Column
، قد يختار Compose تخطّي عناصر LazyColumn
إذا لم يتغيّر names
.
مرة أخرى، يجب أن تكون جميع الدوال أو lambdas القابلة للإنشاء خالية من الآثار الجانبية. عندما تحتاج إلى تنفيذ تأثير جانبي، يمكنك تشغيله من دالة ردّ الاتصال.
إعادة التركيب متفائلة
تبدأ عملية إعادة التركيب عندما يرى Compose أنّ مَعلمات أحد العناصر القابلة للإنشاء قد تكون تغيّرت. تكون إعادة التركيب تفاؤلية، ما يعني أنّ Compose تتوقّع أن تنتهي من إعادة التركيب قبل أن تتغيّر المَعلمات مرة أخرى. إذا تغيّرت مَعلمة قبل انتهاء إعادة التركيب، قد يلغي Compose عملية إعادة التركيب ويعيد تشغيلها باستخدام المَعلمة الجديدة.
عند إلغاء إعادة الإنشاء، يتجاهل Compose شجرة واجهة المستخدم من عملية إعادة الإنشاء. إذا كانت لديك أي آثار جانبية تعتمد على عرض واجهة المستخدم، سيتم تطبيق التأثير الجانبي حتى إذا تم إلغاء التركيب. ويمكن أن يؤدي ذلك إلى حالة غير متسقة للتطبيق.
تأكَّد من أنّ جميع الدوال القابلة للإنشاء وعبارات lambda متكررة وغير مرتبطة بأي آثار جانبية للتعامل مع إعادة التركيب المتفائلة.
قد يتم تشغيل الدوال القابلة للإنشاء بشكل متكرّر
في بعض الحالات، قد يتم تشغيل دالة قابلة للإنشاء لكل إطار من إطارات الرسوم المتحركة لواجهة المستخدم. إذا كانت الدالة تنفّذ عمليات مكلفة، مثل القراءة من مساحة تخزين الجهاز، يمكن أن تتسبب الدالة في حدوث تشوّش في واجهة المستخدم.
على سبيل المثال، إذا حاولت الأداة قراءة إعدادات الجهاز، قد تتمكّن من قراءة هذه الإعدادات مئات المرات في الثانية، ما يؤدي إلى آثار كارثية على أداء تطبيقك.
إذا كانت الدالة البرمجية القابلة للإنشاء تحتاج إلى بيانات، حدِّد مَعلمات لهذه البيانات. يمكنك بعد ذلك نقل العمليات المكلفة إلى سلسلة محادثات أخرى خارج عملية الإنشاء، وتمرير القيمة الناتجة إلى الدالة القابلة للإنشاء كمعلَمة باستخدام 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 قابلة للإنشاء.
يمكن تنفيذ الدوال البرمجية القابلة للإنشاء بأي ترتيب
إذا نظرت إلى الرمز البرمجي لدالة قابلة للإنشاء، قد تفترض أنّ الرمز البرمجي يتم تنفيذه بالترتيب الذي يظهر به. لكن لا يمكن ضمان صحة ذلك. إذا كانت دالة مركّبة تتضمّن طلبات دوال مركّبة أخرى، قد يتم تنفيذ هذه الدوال بأي ترتيب. يتضمّن Compose خيارًا للتعرّف على أنّ بعض عناصر واجهة المستخدم لها أولوية أعلى من غيرها، وبالتالي يتم رسمها أولاً.
على سبيل المثال، لنفترض أنّ لديك رمزًا برمجيًا على النحو التالي لرسم ثلاث شاشات في تخطيط علامات التبويب:
@Composable fun ButtonRow() { MyFancyNavigation { StartScreen() MiddleScreen() EndScreen() } }
قد يتم إجراء المكالمات إلى "StartScreen
" و"MiddleScreen
" و"EndScreen
" بأي ترتيب. هذا يعني أنّه لا يمكنك، على سبيل المثال، أن تجعل StartScreen()
يضبط بعض المتغيّرات العامة (تأثير جانبي) وأن تجعل MiddleScreen()
يستفيد من هذا التغيير. بدلاً من ذلك، يجب أن تكون كل دالة من هذه الدوال مستقلة.
مزيد من المعلومات
لمزيد من المعلومات حول طريقة التفكير في Compose والدوال القابلة للإنشاء، يُرجى الاطّلاع على المراجع الإضافية التالية.
الفيديوهات
مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون JavaScript غير مفعّلة
- Kotlin في Jetpack Compose
- الحالة وJetpack Compose
- الطبقات البنوية في Jetpack Compose