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

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

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

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

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

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

مخطّط بياني يعرض مراحل نشاط عنصر قابل للإنشاء

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

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

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

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

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

الشكل 2. تمثيل MyComposable في "المقطوعة الموسيقية" إذا تم استدعاء عنصر قابل للإنشاء عدة مرات، يتم وضع عدة مثيلات في Composition. يشير العنصر الذي له لون مختلف إلى أنّه نسخة منفصلة.

بنية عنصر قابل للإنشاء في Composition

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

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

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

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

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

سيؤدي استدعاء عنصر قابل للإنشاء عدة مرات إلى إضافته إلى Composition عدة مرات أيضًا. عند استدعاء عنصر قابل للإنشاء عدة مرات من موقع الاستدعاء نفسه، لا تتوفّر لدى 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 ترتيب التنفيذ بالإضافة إلى موقع الاستدعاء للحفاظ على تمييز المثيل في Composition. إذا تمت إضافة movie جديد إلى أسفل القائمة، يمكن أن تعيد Compose استخدام العناصر التي سبق أن تم إنشاؤها في Composition لأنّ موضعها في القائمة لم يتغيّر، وبالتالي، تكون قيمة الإدخال movie هي نفسها بالنسبة إلى هذه العناصر.

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

الشكل 4 تمثيل MoviesScreen في التركيب عند إضافة عنصر جديد إلى أسفل القائمة يمكن إعادة استخدام دوال MovieOverview البرمجية القابلة للإنشاء في Composition. يشير اللون نفسه في 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 في Composition عند إضافة عنصر جديد إلى القائمة. لا يمكن إعادة استخدام عناصر MovieOverview القابلة للإنشاء، وستتم إعادة تشغيل جميع التأثيرات الجانبية. يشير اللون المختلف في MovieOverview إلى أنّه تمت إعادة إنشاء العنصر القابل للإنشاء.

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

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

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

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

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

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

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

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

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

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