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

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

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

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

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

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

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

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

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

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

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

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