Composition هو العملية المركزية في Jetpack Compose، حيث يتم من خلالها بناء شجرة واجهة مستخدم حية تُعرض على الشاشة من دوال Composable الوصفية. على عكس نظام View في Android، حيث يتم تحميل التخطيطات من XML وتحويلها إلى كائنات غير قابلة للتغيير، يعمل Composition كنظام ديناميكي: يتم تنفيذ الدوال، وإنشاء فتحات في الذاكرة، وتشكيل تسلسل هرمي للعقد، وربطه بالحالة. وفقًا لـ Google Android Developers, 2026، فإن فهم Composition أمر بالغ الأهمية لتحسين أداء تطبيقات Compose.
الرئيسية
Composition هو عملية تنفيذ دوال Composable، مما يؤدي إلى تمثيل داخلي لواجهة المستخدم على شكل شجرة من العقد. كل عقدة في هذه الشجرة تتوافق إما مع مكون مدمج (Text, Button, Image) أو مع استدعاء دالة Composable معرفة من قبل المستخدم. Composition لا ينشئ كائنات View مباشرة في Android — بل يبني وصفًا مجردًا يتم معالجته بعد ذلك بواسطة مرحلتي Layout وDrawing.
السمة الرئيسية لـ Composition هي قابلية إعادة التشغيل. يمكن إعادة تشغيل كل دالة Composable داخل التركيب في أي وقت إذا تغيرت معاملات الإدخال الخاصة بها أو كائنات الحالة التي تقرأها. النظام لا يعيد تشغيل الشجرة بأكملها — فقط تلك الدوال التي تعتمد فعليًا على البيانات المتغيرة.
من الناحية الفنية، يتم إدارة Composition من خلال Composer — محرك داخلي يدمجه مترجم Kotlin في كل دالة Composable. يقوم Composer بتسجيل معلومات حول أي الدوال تم استدعاؤها، وبأي معاملات، وبأي ترتيب في فتحات (مجموعات موضع). في الاستدعاءات اللاحقة، يقارن Composer البيانات الجديدة مع البيانات المخزنة ويقرر ما إذا كان سيعيد التشغيل.
تبدأ عملية بناء شجرة واجهة المستخدم باستدعاء طريقة setContent داخل Activity أو Fragment. تنشئ هذه الطريقة Composition الأولي وتبدأ في تنفيذ دالة Composable الجذر. ثم تقوم كل دالة Composable متداخلة بإضافة عقدها إلى الشجرة، مشكلة تسلسلاً هرميًا: Row يحتوي على Text وButton، وColumn يحتوي على Image وCard، وهكذا.
تتلقى كل عقدة في الشجرة مفتاح موضع فريد، بناءً على موقعها في الكود المصدري. يُستخدم هذا المفتاح لتحديد العقدة أثناء عمليات التنفيذ اللاحقة. مفتاح الموضع هو السبب في أن ترتيب استدعاء دوال Composable يجب ألا يعتمد على الشروط: إذا تم في تشغيل واحد استدعاء A -> B، وفي التشغيل التالي B -> A، فلن يتمكن Compose من مطابقة العقد القديمة والجديدة.
@Composable
fun AppScreen() {
Column { // عقدة Column (الموضع 1)
HeaderSection() // عقدة HeaderSection (الموضع 2)
ContentSection() // عقدة ContentSection (الموضع 3)
FooterSection() // عقدة FooterSection (الموضع 4)
}
}
@Composable
fun HeaderSection() {
Row { // عقدة Row (الموضع 2.1)
Text("عنوان") // عقدة Text (الموضع 2.2)
Icon(...) // عقدة Icon (الموضع 2.3)
}
}
في هذا المثال، يتلقى كل استدعاء موضعًا بناءً على الترتيب في الكود. Column (الموضع 1) يحتوي على ثلاث عقد فرعية (المواضع 2، 3، 4). HeaderSection يضيف عقدتين فرعيتين إضافيتين (2.1، 2.2، 2.3). إذا تم في إعادة التركيب التالية استدعاء ContentSection قبل HeaderSection، فلن يتمكن Composer من مطابقة العقد بشكل صحيح — ومن هنا القاعدة: يجب أن يكون ترتيب استدعاءات دوال Composable مستقرًا.
تتم إدارة الحالة في Composition من خلال كائنات من النوع State<T>. عندما تقرأ دالة Composable قيمة من State عبر خاصية مفوضة (by)، فإنها تسجل اعتمادًا على هذا State. عندما تتغير القيمة، يتم وضع علامة على جميع الدوال التي قرأت هذا State لإعادة التشغيل في المرحلة التالية من التركيب.
تسمى آلية تسجيل التبعيات نظام snapshot. في كل مرة يتغير فيها State، يسجل snapshot جميع التغييرات ويخطر Composer بالدوال التي تعتمد على هذا State. من المهم أن نفهم: قراءة State داخل كود غير Composable (مثلًا في لامدا onClick) لا تسجل اعتمادًا — فقط القراءة داخل دالة Composable أو في لامدات تُنفذ في سياق التركيب.
يعمل نظام snapshot بشكل تعامدي: يتم دمج تغييرات State المتعددة ضمن حدث واحد في معاملة واحدة، مما يمنع إعادة التركيب المتعددة. هذا مهم بشكل خاص عند معالجة الإيماءات: حركة واحدة تغير عدة كائنات State، لكن Compose ينفذ إعادة تركيب واحدة فقط.
@Composable
fun StateExample() {
var text by remember { mutableStateOf("Hello") }
var isVisible by remember { mutableStateOf(true) }
Column {
Text(text) // يسجل اعتمادًا على النص
if (isVisible) { // يسجل اعتمادًا على isVisible
TextField(value = text, onValueChange = { text = it })
}
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "إخفاء" else "إظهار")
}
}
}
تغيير النص يؤدي إلى إعادة تركيب Column وText وTextField فقط. تبقى Column وButton وشرط isVisible دون تغيير. هذا العزل لإعادة التركيب هو ميزة رئيسية لـ Compose مقارنة بالأنظمة التي تعيد رسم الشاشة بأكملها. كل دالة Composable تتبع فقط كائنات State التي تقرأها مباشرة.
Composition وRecomposition هما وضعان مختلفان لتنفيذ دوال Composable. يحدث Composition مرة واحدة عند إنشاء الشاشة: ينفذ النظام جميع دوال Composable بقيم أولية ويبني شجرة واجهة المستخدم الأولية. يحدث Recomposition عدة مرات عند تغيير البيانات: يعيد تشغيل النظام فقط تلك الدوال التي تعتمد على الحالة المتغيرة.
الوضع Composition ينشط جميع عقد الشجرة، ويخصص فتحات لكل دالة، ويسجل جميع الفروع. يعمل Recomposition بشكل انتقائي: يقارن Compose القيم الجديدة والقديمة لمعاملات كل دالة، وإذا لم تتغير — لا يتم تنفيذ الدالة (skipping).
يختلف Composition وRecomposition في التكلفة. أول Composition أكثر تكلفة لأنه يتطلب بناء الشجرة بالكامل وتخصيص الفتحات. Recomposition أقل تكلفة، خاصة إذا كانت معظم الدوال مستقرة — تتم مقارنة معاملاتها باستخدام equals، ويتخطى Compose استدعاءها. للحصول على أقصى أداء، يجب أن تسعى إلى أن تؤثر معظم إعادة التركيبات على أقل عدد ممكن من الدوال.
| الخاصية | Composition | Recomposition |
|---|---|---|
| عندما يحدث | مرة واحدة، عند أول عرض | عدة مرات، عند تغيير البيانات |
| النطاق | الشجرة بأكملها | فقط الدوال المتغيرة |
| مقارنة المعاملات | لا تتم | تتم من أجل التخطي |
| إنشاء الفتحات | نعم، جميع الفتحات تُنشأ | فقط للعقد الجديدة |
CompositionLocal هي آلية لنقل البيانات ضمنيًا عبر شجرة التركيب. تحل المشكلة عندما يلزم تمرير معامل عبر عشرات دوال Composable المتداخلة التي لا تستخدمه مباشرة. بدلاً من سلسلة معاملات صريحة، يتم تعيين البيانات على المستوى الأعلى وقراءتها في أي دالة متداخلة عبر CompositionLocal.current.
MaterialTheme هو المثال الأكثر شهرة لـ CompositionLocal. جميع مكونات Compose تقرأ الألوان والطباعة والأشكال عبر MaterialTheme.colorScheme وMaterialTheme.typography وMaterialTheme.shapes، دون استلامها عبر معاملات. يمكن للمطورين إنشاء CompositionLocal خاص بهم لبيانات مثل المستخدم الحالي أو إعدادات التوطين أو تكوين الشاشة.
قيد مهم: لا ينبغي استخدام CompositionLocal للبيانات المتغيرة بشكل متكرر (موضع التمرير، النص في حقل الإدخال). المكون الذي يقرأ CompositionLocal يعاد تشغيله في كل مرة تتغير فيها القيمة، لذا بالنسبة للبيانات الديناميكية من الأفضل استخدام معاملات صريحة أو State. CompositionLocal مثالي لبيانات التكوين التي تتغير نادرًا أو لا تتغير أبدًا.
val LocalUser = compositionLocalOf<User?> { null }
@Composable
fun AppRoot(user: User, content: @Composable () -> Unit) {
CompositionLocalProvider(LocalUser.provides(user)) {
content()
}
}
@Composable
fun UserAvatar() {
val user = LocalUser.current // قراءة بدون معامل صريح
AsyncImage(model = user?.avatarUrl, contentDescription = "Avatar")
}
CompositionLocalProvider ينشئ نطاقًا يعيد فيه LocalUser.current القيمة المحددة. UserAvatar يقرأ المستخدم دون تمرير المعامل بشكل صريح عبر الدوال الوسيطة. هذا ذو قيمة خاصة في التسلسلات الهرمية العميقة حيث تكون البيانات مطلوبة فقط في عدد قليل من العقد الطرفية.
الأسئلة الشائعة
تغيير State أثناء Composition يجدول إعادة تركيب جديدة، والتي سيتم تنفيذها بعد اكتمال الحالية. لا يحدث حلقة لا نهائية: يضمن Compose أن كل إعادة تركيب تتم في معاملة منفصلة لنظام snapshot.
على الأجهزة الحديثة، Composition لشاشة تحتوي على 50–100 دالة Composable يستغرق 1–5 مللي ثانية. توصي Google بالبقاء ضمن 16 مللي ثانية لإطار 60 إطارًا في الثانية. إذا تجاوز Composition هذا الحد، استخدم LazyColumn أو قسم الشاشة إلى دوال أصغر.
البدء اليدوي المباشر لـ Composition غير ممكن — يتم إدارته بواسطة Composer تلقائيًا. ومع ذلك، يمكنك فرض إعادة تركيب عن طريق تغيير State أو استدعاء invalidate() على composable الجذر إذا كان لديك وصول إلى CompositionContext.
التسلسل الهرمي View هو شجرة غير قابلة للتغيير من كائنات Java يتم إنشاؤها مرة واحدة. Composition هو شجرة افتراضية يتم إعادة بنائها في كل مرة تتغير فيها البيانات. View يخزن حالته في متغيرات المثيل، Composition — في فتحات مرتبطة بموضع استدعاء الدالة.
إذا توقفت دالة Composable عن الاستدعاء (على سبيل المثال، أصبح شرط if false)، فإن Composition يزيل عقدتها ويطلق تنظيف DisposableEffect. عند إعادة ظهورها (if يصبح true مرة أخرى)، يتم إنشاء عقدة جديدة — ولا تتم استعادة القديمة.
الملخص
سنقوم بتطوير تطبيق جوال جاهز
تقدم IT Sectr تطبيقات iOS وAndroid للشركات الناشئة والشركات منذ عام 2017. سوف نقدم لك النصح ونقترح أفضل حل.
اقرأ أيضًا