مراحل نشاط التركيبات

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

نظرة عامة على مراحل النشاط

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

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

ولا يمكن إنتاج مقطوعة موسيقية إلا من خلال مقطوعة موسيقية أولية ويتم تعديلها من خلال إعادة الإنشاء. إنّ الطريقة الوحيدة لتعديل المقطوعة الموسيقية هي من خلال إعادة تركيبها.

مخطّط بياني يعرض رحلة التفاعل مع عنصر قابل للتجميع

الشكل 1: دورة حياة عنصر قابل للتجميع في التركيب يدخل المقطع إلى المقطوعة الموسيقية، ويتم إعادة إنشائه 0 مرة أو أكثر، ويغادر المقطوعة الموسيقية.

يتم عادةً بدء عملية إعادة التركيب نتيجةً لتغيير في كائن State<T>. يمكنك إنشاء مقاطع صوتية ثم إعادة تشغيل كل العناصر القابلة للإنشاء في المقطوعة الموسيقية التي تتضمّن State<T> الخاص، وأي عناصر قابلة للإنشاء تم تسميتها ولا يمكن تخطيها.

في حال استدعاء عنصر قابل للتجميع عدة مرات، يتم وضع نُسخ متعددة منه في التركيب. ولكلّ مكالمة دورة حياة خاصة بها في التركيب.

@Composable
fun MyComposable() {
    Column {
        Text("Hello")
        Text("World")
    }
}

مخطّط بياني يعرض الترتيب الهرمي للعناصر في المقتطف السابق من الرمز

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

بنية العنصر القابل للتجميع في "التركيب"

يتم تحديد مثيل العنصر القابل للتجميع في التكوين من خلال موقع الاستدعاء. يعتبر مُجمِّع Compose كل موقع اتصال مختلفًا. سيؤدي استدعاء العناصر القابلة للتجميع من مواقع اتصال متعددة إلى إنشاء مثيلات متعددة للعنصر القابل للتجميع في التركيب.

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

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

راجِع المثال التالي:

@Composable
fun LoginScreen(showError: Boolean) {
    if (showError) {
        LoginError()
    }
    LoginInput() // This call site affects where LoginInput is placed in Composition
}

@Composable
fun LoginInput() { /* ... */ }

@Composable
fun LoginError() { /* ... */ }

في مقتطف الرمز أعلاه، سيستدعي LoginScreen بشكل مشروط العنصر القابل للتجميع LoginError وسيستدعي دائمًا العنصر القابل للتجميع LoginInput. ولكل دعوة موقع دعوة وموقع مصدر فريدَين، وسيستخدمهما المُجمِّع لتحديدها بشكل فريد.

مخطّط بياني يوضّح كيفية إعادة تكوين الرمز البرمجي السابق في حال تغيير علامة showError إلى true تتم إضافة العنصر LoginError، ولكن لا تتم إعادة تركيب العناصر الأخرى.

الشكل 3. تمثيل LoginScreen في التركيب عند تغيُّر الحالة وإعادة التركيب يشير اللون نفسه إلى أنّه لم تتم إعادة تركيبه.

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

إضافة معلومات إضافية للمساعدة في عمليات إعادة التركيب الذكية

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

@Composable
fun MoviesScreen(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            // MovieOverview composables are placed in Composition given its
            // index position in the for loop
            MovieOverview(movie)
        }
    }
}

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

مخطّط بياني يوضّح كيفية إعادة تكوين الرمز البرمجي السابق في حال إضافة عنصر جديد إلى أسفل القائمة لم يتغيّر موضع العناصر الأخرى في القائمة ولم يُعاد إنشاؤها.

الشكل 4: تمثيل MoviesScreen في التركيب عند إضافة عنصر جديد إلى أسفل القائمة يمكن إعادة استخدام MovieOverview عنصر قابل للتجميع في التركيب. يشير استخدام اللون نفسه في MovieOverview إلى أنّه لم تتم إعادة إنشاء العنصر القابل للإنشاء.

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

@Composable
fun MovieOverview(movie: Movie) {
    Column {
        // Side effect explained later in the docs. If MovieOverview
        // recomposes, while fetching the image is in progress,
        // it is cancelled and restarted.
        val image = loadNetworkImage(movie.url)
        MovieHeader(image)

        /* ... */
    }
}

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

الشكل 5: تمثيل MoviesScreen في التركيب عند إضافة عنصر جديد إلى القائمة لا يمكن إعادة استخدام العناصر القابلة للتجميع في MovieOverview، وسيتم إعادة تشغيل جميع التأثيرات الجانبية. يشير اللون المختلف في MovieOverview إلى أنّه تمت إعادة تركيب العنصر القابل للتجميع.

من الناحية المثالية، نريد اعتبار هوية مثيل MovieOverview مرتبطة بهوية movie التي يتم تمريرها إليه. إذا أعدنا ترتيب قائمة الأفلام، من المفترض أن نعيد ترتيب المثيلات في شجرة المقطوعات الموسيقية بدلاً من إعادة إنشاء كل MovieOverview قابل للإنشاء باستخدام مثيل فيلم مختلف. توفّر Compose طريقة لإعلام وقت التشغيل بالقيم التي تريد استخدامها لتحديد جزء معيّن من الشجرة: key composable.

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

@Composable
fun MoviesScreenWithKey(movies: List<Movie>) {
    Column {
        for (movie in movies) {
            key(movie.id) { // Unique ID for this movie
                MovieOverview(movie)
            }
        }
    }
}

باستخدام ما سبق، حتى إذا تغيّرت العناصر في القائمة، سيتعرّف Compose على المكالمات الفردية إلى MovieOverview ويمكنه إعادة استخدامها.

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

الشكل 6: تمثيل MoviesScreen في التركيب عند إضافة عنصر جديد إلى القائمة بما أنّ العناصر القابلة للتجميع من MovieOverview لها مفاتيح فريدة، يتعرّف Compose على نُسخ MovieOverview التي لم تتغيّر، وي يمكنه إعادة استخدامها، وسيستمر تنفيذ تأثيراتها الجانبية.

تتوفّر لبعض العناصر القابلة للتجميع ميزة مدمجة تتيح استخدام العنصر القابل للتجميع key. على سبيل المثال، يقبل LazyColumn تحديد key مخصّص في items DSL.

@Composable
fun MoviesScreenLazy(movies: List<Movie>) {
    LazyColumn {
        items(movies, key = { movie -> movie.id }) { movie ->
            MovieOverview(movie)
        }
    }
}

التخطّي إذا لم تتغيّر الإدخالات

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

تكون الدالة المركّبة مؤهّلة للتخطّي إلا إذا:

  • نوع الإرجاع للدالة ليس Unit
  • تتم إضافة تعليقات توضيحية إلى الدالة باستخدام @NonRestartableComposable أو @NonSkippableComposable.
  • المَعلمة المطلوبة من نوع غير ثابت

هناك وضع تجريبي للمحول البرمجي، التخطي القوي، يخفف من المتطلبات الأخيرة.

لكي يُعتبر النوع ثابتًا، يجب أن يمتثل للعقد التالي:

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

هناك بعض الأنواع الشائعة المهمة التي تندرج ضمن هذا العقد والتي سيتعامل معها المحول البرمجي في Compose على أنّها ثابتة، على الرغم من عدم الإشارة صراحةً إلى أنّها ثابتة باستخدام تعليق @Stable التوضيحي:

  • جميع أنواع القيم الأساسية: Boolean وInt وLong وFloat وChar وما إلى ذلك
  • الأوتار
  • جميع أنواع الدوالّ (دالّات لامبادا)

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

من بين الأنواع البارزة التي تكون ثابتة ولكن يمكن تغييرها، نوع MutableState في Compose. إذا تم الاحتفاظ بقيمة في MutableState، سيتم اعتبار عنصر الحالة بشكل عام ثابتًا لأنّه سيتم إشعار Compose بأي تغييرات تطرأ على السمة .value في State.

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

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

إذا لم تتمكّن أداة Compose من استنتاج أنّ نوعًا معيّنًا ثابت، ولكنّك تريد إجبار Compose على التعامل معه على أنّه ثابت، يمكنك وضع علامة عليه باستخدام التعليق التوضيحي @Stable.

// Marking the type as stable to favor skipping and smart recompositions.
@Stable
interface UiState<T : Result<T>> {
    val value: T?
    val exception: Throwable?

    val hasError: Boolean
        get() = exception != null
}

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