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

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

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

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

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

لا يمكن إنشاء تركيبة إلا من خلال تركيبة أولية وتعديلها من خلال إعادة التركيب. لا يمكن تعديل تركيبة إلا من خلال إعادة التركيب.

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

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

يتم عادةً بدء عملية إعادة التركيب نتيجةً لتغيير في كائن State<T>. تتتبّع Compose هذه العناصر وتعمل على تشغيل جميع العناصر القابلة للتجميع في التركيب التي تقرأ 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 جديد إلى أسفل القائمة، يمكن لميزة "الإنشاء" إعادة استخدام النُسخ التي سبق أن تم تضمينها في التركيبة لأنّ موقعها في القائمة لم يتغيّر، وبالتالي، فإنّ إدخال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)
            }
        }
    }
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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