التفكير في الإنشاء

Jetpack Compose هي مجموعة أدوات تعريفية حديثة لواجهة المستخدم لنظام التشغيل Android. يسهِّل Compose كتابة محتوى واجهة المستخدم وصيانته من خلال توفير واجهة برمجة تطبيقات بيانية تسمح لك بعرض واجهة المستخدم للتطبيق بدون التغيير الضروري لطرق عرض الواجهة الأمامية. يحتاج هذا المصطلح إلى بعض التفسير، لكن الآثار المترتبة على ذلك مهمة لتصميم تطبيقك.

نموذج البرمجة التصريحية

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

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

على مدار السنوات العديدة الماضية، بدأ المجال بأكمله في التحول إلى نموذج بياني لواجهة المستخدم، والذي يبسط بشكل كبير الهندسة المرتبطة ببناء واجهات المستخدم وتحديثها. تعمل هذه التقنية من خلال تجديد الشاشة بأكملها من البداية، ثم تطبيق التغييرات الضرورية فقط. يتجنّب هذا النهج تعقيدات التعديل اليدوي للتسلسل الهرمي لطريقة عرض الحالة. الإنشاء هو إطار عمل تعريفي لواجهة المستخدم.

يتمثل أحد التحديات في إعادة إنشاء الشاشة بأكملها في أنها قد تكون مكلفة من حيث الوقت وطاقة الحوسبة واستخدام البطارية. للتخفيف من هذه التكلفة، تختار Compose بذكاء أجزاء واجهة المستخدم التي تحتاج إلى إعادة رسمها في أي وقت. لهذا الأمر بعض الآثار على كيفية تصميم مكونات واجهة المستخدم، كما هو موضَّح في مقالة إعادة الإنشاء.

دالة بسيطة قابلة للإنشاء

باستخدام Compose، يمكنك إنشاء واجهة مستخدم عن طريق تحديد مجموعة من الدوال القابلة للإنشاء التي تستقبل البيانات وتصدر عناصر من واجهة المستخدم. مثال بسيط هو تطبيق Greeting المصغّر الذي يستخدم String ويصدر التطبيق المصغّر Text الذي يعرض رسالة ترحيب.

لقطة شاشة لهاتف يعرض النص

الشكل 1. دالة بسيطة قابلة للإنشاء يتم تمرير البيانات واستخدامها لعرض أداة نصية على الشاشة.

في ما يلي بعض المعلومات المهمة حول هذه الدالة:

  • تتم إضافة تعليق توضيحي للدالة باستخدام تعليق @Composable التوضيحي. يجب أن يتوفر هذا التعليق التوضيحي في جميع الدوال القابلة للإنشاء، ويُعلم هذا التعليق التوضيحي برنامج التحويل Compose بأنّ هذه الدالة تهدف إلى تحويل البيانات إلى واجهة المستخدم.

  • تأخذ الدالة البيانات. يمكن للدوال القابلة للإنشاء قبول المعلمات، والتي تسمح لمنطق التطبيق بوصف واجهة المستخدم. في هذه الحالة، تقبل الأداة String بحيث يمكنها الترحيب بالمستخدم بالاسم.

  • تعرض الدالة نصًا في واجهة المستخدم. ويتم ذلك عن طريق استدعاء الدالة Text() القابلة للإنشاء، التي تنشئ في الواقع عنصر واجهة المستخدم النصي. تنبعث الدوال القابلة للإنشاء من التسلسل الهرمي لواجهة المستخدم عن طريق استدعاء دوال أخرى قابلة للإنشاء.

  • لا تُرجع الدالة أي شيء. إنشاء دوال تصدر من واجهة المستخدم

  • وتكون هذه الوظيفة سريعة وغير نشِطة وخالية من الآثار الجانبية.

    • تعمل الدالة بنفس الطريقة عند طلبها عدة مرات باستخدام الوسيطة نفسها، ولا تستخدم قيمًا أخرى مثل المتغيرات العمومية أو استدعاءات random().
    • تصف الدالة واجهة المستخدم بدون أي آثار جانبية، مثل تعديل الخصائص أو المتغيرات العمومية.

    بشكل عام، يجب كتابة جميع الدوال القابلة للإنشاء باستخدام هذه الخصائص، وذلك لأسباب تمت مناقشتها في إعادة الإنشاء.

التحوّل في النموذج التصريحي

مع العديد من حزم أدوات واجهة المستخدم الضرورية الموجّهة للكائنات، يمكنك تهيئة واجهة المستخدم من خلال توضيح شجرة من التطبيقات المصغّرة. غالبًا ما تفعل ذلك عن طريق تضخيم ملف تخطيط XML. تحتفظ كل أداة بحالتها الداخلية الخاصة، وتعرض طرق الاستلام والضبط التي تسمح لمنطق التطبيق بالتفاعل مع الأداة.

في النهج الوصفي في Compose، تكون التطبيقات المصغّرة عديمة الحالة نسبيًا ولا تعرض دوال setter أو getter. في الواقع، لا يتم عرض الأدوات ككائنات. يمكنك تحديث واجهة المستخدم عن طريق استدعاء نفس الدالة القابلة للإنشاء بوسيطات مختلفة. ويسهِّل ذلك توضيح حالة الأنماط المعمارية مثل ViewModel، كما هو موضَّح في دليل بنية التطبيق. بعد ذلك، تكون العناصر القابلة للإنشاء مسؤولة عن تحويل حالة التطبيق الحالية إلى واجهة مستخدم في كل مرة يتم فيها تحديث البيانات القابلة للملاحظة.

رسم توضيحي لتدفق البيانات في واجهة المستخدم في Compose، من العناصر عالية المستوى إلى العناصر الثانوية

الشكل 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 مرة أخرى لعرض القيمة الجديدة، وتسمّى هذه العملية إعادة الإنشاء. ولا تتم إعادة تركيب الدوال الأخرى التي لا تعتمد على القيمة.

كما ناقشنا، يمكن أن تكون إعادة صياغة شجرة واجهة المستخدم بأكملها مكلفة من الناحية الحسابية، الأمر الذي يستخدم طاقة الحوسبة وعمر البطارية. يحل Compose هذه المشكلة من خلال إعادة التركيب الذكية هذه.

إعادة الإنشاء هي عملية استدعاء الدوال القابلة للإنشاء مرة أخرى عند تغيير الإدخالات. ويحدث ذلك عندما تتغير مدخلات الدالة. عندما تتم إعادة الإنشاء في Compose بناءً على المدخلات الجديدة، فإنها تستدعي فقط الدوال أو lambdas التي ربما تكون قد تغيرت، وتتخطى الباقي. من خلال تخطي جميع الدوال أو ملفات lambda التي لم يتم تغيير معلَماتها، يمكن إنشاء Compose بكفاءة.

لا تعتمد أبدًا على الآثار الجانبية لتنفيذ الدوال القابلة للإنشاء، لأنه قد يتم تخطي إعادة تكوين الدالة. في حال إجراء ذلك، قد يواجه المستخدمون سلوكًا غريبًا وغير متوقّع في تطبيقك. ويتمثل الأثر الجانبي في أي تغيير يظهر لبقية تطبيقك. على سبيل المثال، تشمل هذه الإجراءات آثارًا جانبية خطيرة:

  • الكتابة في خاصية لكائن مشترك
  • جارٍ تعديل عنصر ملاحظ في ViewModel.
  • جارٍ تعديل الإعدادات المفضّلة المشتركة

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

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

@Composable
fun SharedPrefsToggle(
    text: String,
    value: Boolean,
    onValueChanged: (Boolean) -> Unit
) {
    Row {
        Text(text)
        Checkbox(checked = value, onCheckedChange = onValueChanged)
    }
}

يناقش هذا المستند عددًا من الأمور التي يجب أن تكون على دراية بها عند استخدام Compose:

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

ستتناول الأقسام التالية كيفية إنشاء دوال قابلة للإنشاء لدعم إعادة الإنشاء. من أفضل الممارسات في جميع الأحوال هي الحفاظ على سرعة أداء الدوال القابلة للإنشاء وجعلها سريعة الاستجابة وخالية من أي أثر سلبي.

يمكن تنفيذ الدوال القابلة للإنشاء بأي ترتيب.

إذا نظرت إلى التعليمات البرمجية لدالة قابلة للإنشاء، فقد تفترض أنه يتم تشغيل التعليمة البرمجية بالترتيب الذي تظهر به. لكن هذا ليس صحيحًا بالضرورة. إذا كانت الدالة القابلة للإنشاء تحتوي على استدعاءات لدوال أخرى قابلة للإنشاء، فقد يتم تشغيل هذه الدوال بأي ترتيب. يحتوي Compose على خيار إدراك أن بعض عناصر واجهة المستخدم لها أولوية أعلى من غيرها، ورسمها أولاً.

على سبيل المثال، افترض أن لديك تعليمات برمجية مثل هذه لرسم ثلاث شاشات في تخطيط علامة تبويب:

@Composable
fun ButtonRow() {
    MyFancyNavigation {
        StartScreen()
        MiddleScreen()
        EndScreen()
    }
}

قد يتم إرسال المكالمات إلى StartScreen وMiddleScreen وEndScreen بأي ترتيب. وهذا يعني أنّه لا يمكنك مثلاً ضبط StartScreen() متغيّر عمومي (تأثير جانبي) والاستفادة من هذا التغيير في MiddleScreen(). بدلاً من ذلك، يجب أن تكون كل دالة من هذه الدوال قائمة بذاتها.

يمكن تشغيل الدوال القابلة للإنشاء بالتوازي.

يمكن لميزة ComposeAllowed تحسين إعادة التركيب من خلال تشغيل دوال قابلة للإنشاء بالتوازي. يتيح ذلك لتطبيق 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
@Deprecated("Example with bug")
fun ListWithBug(myList: List<String>) {
    var items = 0

    Row(horizontalArrangement = Arrangement.SpaceBetween) {
        Column {
            for (item in myList) {
                Text("Item: $item")
                items++ // Avoid! Side-effect of the column recomposing.
            }
        }
        Text("Count: $items")
    }
}

في هذا المثال، يتم تعديل items مع كل عملية إعادة تركيب. يمكن أن يكون ذلك كل إطار من الرسوم المتحركة، أو عندما يتم تحديث القائمة. وفي كلتا الحالتين، ستعرض واجهة المستخدم العدد الخاطئ. لهذا السبب، لا تتوفّر عمليات كتابة مثل هذه في Compose. ومن خلال حظر هذه الكتابة، نسمح لإطار العمل بتغيير سلاسل المحادثات لتنفيذ دالة lambda قابلة للإنشاء.

يتم تخطي إعادة الإنشاء قدر الإمكان

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

قد تتم إعادة إنشاء كل دالة قابلة للإنشاء ولامبدا من تلقاء نفسها. إليك مثال يوضح كيف يمكن لإعادة التركيب تخطي بعض العناصر عند عرض القائمة:

/**
 * 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)
        Divider()

        // 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 إلى Column بدون تنفيذ أي من عناصرها الرئيسية عند تغيير header. وعند تنفيذ Column، قد يختار Compose تخطّي عناصر LazyColumn في حال عدم تغيير names.

مرة أخرى، يجب أن يكون تنفيذ جميع الدوال القابلة للإنشاء أو lambdas خالٍ من الآثار الجانبية. عندما تحتاج إلى تنفيذ تأثير جانبي، عليك تفعيله من طلب معاودة الاتصال.

إعادة الإنشاء بالتفاؤل

تبدأ عملية إعادة الإنشاء عندما يعتقد Compose أنّ معلمات العنصر القابل للإنشاء ربما قد تغيرت. إعادة الإنشاء هي متفائل، مما يعني أن Compose تتوقع الانتهاء من إعادة الإنشاء قبل تغيير المعلمات مرة أخرى. في حال تغيّر المَعلمة قبل انتهاء عملية إعادة الإنشاء، قد تلغي Compose عملية إعادة الإنشاء وتعيد تشغيلها بالمَعلمة الجديدة.

عند إلغاء إعادة الإنشاء، يتجاهل Compose شجرة واجهة المستخدم من عملية إعادة الإنشاء. في حال حدوث أي آثار جانبية تعتمد على واجهة المستخدم التي يتم عرضها، سيتم تطبيق الأثر الجانبي حتى إذا تم إلغاء المقطوعة. وقد يؤدي ذلك إلى عدم اتّساق حالة التطبيق.

يُرجى التأكّد من أنّ جميع الدوال القابلة للإنشاء واللمداس ثابتة وخالية من الأثر الجانبي للتعامل مع إعادة التركيب المتفائل.

قد يتم تشغيل الدوال القابلة للإنشاء بشكل متكرّر جدًا.

في بعض الحالات، قد يتم تشغيل دالة قابلة للإنشاء لكل إطار من الرسوم المتحركة لواجهة المستخدم. إذا كانت الدالة تؤدي عمليات مكلفة، مثل القراءة من مساحة تخزين الجهاز، يمكن أن تتسبّب الدالة في إيقاف مؤقت لواجهة المستخدم.

على سبيل المثال، إذا حاول التطبيق المصغّر قراءة إعدادات الجهاز، من المحتمل أن يقرأ هذه الإعدادات مئات المرات في الثانية، مع تأثيرات كارثية على أداء تطبيقك.

إذا كانت الدالة القابلة للإنشاء تحتاج إلى بيانات، يجب أن تحدِّد مَعلمات للبيانات. يمكنك بعد ذلك نقل العمل المكلف إلى سلسلة محادثات أخرى خارج نطاق التركيبة، وتمرير البيانات إلى أداة Compose باستخدام mutableStateOf أو LiveData.

مزيد من المعلومات

لمعرفة المزيد حول كيفية التفكير في Compose ودوال قابلة للإنشاء، اطلع على الموارد الإضافية التالية.

الفيديوهات الطويلة