Composition — فرآیند مرکزی در Jetpack Compose است که طی آن از توابع توصیفی Composable یک درخت UI زنده ساخته میشود که روی صفحه نمایش داده میشود. برخلاف سیستم View اندروید، که طرحبندی از XML بارگذاری و به اشیاء تغییرناپذیر تبدیل میشد، Composition به عنوان یک سیستم پویا کار میکند: توابع اجرا میشوند، slotهایی در حافظه ایجاد میکنند، سلسلهمراتب گرهها را شکل میدهند و آن را به وضعیت متصل میکنند. به گفته Google Android Developers, 2026، درک Composition برای بهینهسازی عملکرد برنامههای Compose حیاتی است.
نکات کلیدی
Composition — فرآیند اجرای توابع Composable است که در نتیجه آن نمایش داخلی رابط کاربری به صورت درختی از گرهها شکل میگیرد. هر گره این درخت یا با یک کامپوننت داخلی (Text, Button, Image) یا با فراخوانی یک تابع Composable سفارشی مطابقت دارد. Composition مستقیماً اشیاء View اندروید را ایجاد نمیکند — بلکه یک توصیف انتزاعی میسازد که سپس توسط فازهای Layout و Drawing پردازش میشود.
ویژگی کلیدی Composition — قابلیت راهاندازی مجدد (restartability) آن است. هر تابع Composable در ترکیب میتواند در هر لحظه دوباره راهاندازی شود، اگر پارامترهای ورودی یا اشیاء وضعیت خوانده شده توسط آن تغییر کرده باشند. سیستم کل درخت را راهاندازی مجدد نمیکند — فقط توابعی که واقعاً به دادههای تغییر یافته وابسته هستند.
از نظر فنی، Composition توسط Composer مدیریت میشود — موتور داخلی که کامپایلر Kotlin در هر تابع Composable جاسازی میکند. Composer در slotها (گروههای موقعیت) اطلاعاتی درباره اینکه کدام توابع با چه پارامترهایی و به چه ترتیبی فراخوانی شدهاند، ثبت میکند. در فراخوانیهای بعدی، Composer دادههای جدید را با دادههای ذخیره شده مقایسه کرده و درباره راهاندازی مجدد تصمیم میگیرد.
فرآیند ساخت درخت UI با فراخوانی متد 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). اگر در recomposition بعدی ContentSection قبل از HeaderSection فراخوانی شود، Composer قادر به تطبیق صحیح گرهها نخواهد بود — از این رو قانون: ترتیب فراخوانی توابع Composable باید پایدار باشد.
وضعیت در Composition از طریق اشیاء از نوع State<T> مدیریت میشود. وقتی یک تابع Composable مقدار را از State از طریق خاصیت تفویض شده (by) میخواند، وابستگی به آن State را ثبت میکند. هنگام تغییر مقدار، تمام توابعی که این State را خواندهاند برای راهاندازی مجدد در فاز بعدی ترکیب علامتگذاری میشوند.
مکانیسم ثبت وابستگیها سیستم snapshot نامیده میشود. هر بار که State تغییر میکند، snapshot تمام تغییرات را ثبت کرده و به Composer اطلاع میدهد که کدام توابع به این State وابسته هستند. درک این نکته مهم است: خواندن State در کد غیر Composable (مثلاً در لامبدای onClick) وابستگی را ثبت نمیکند — فقط خواندن داخل تابع Composable یا در لامبداهایی که در بافت ترکیب اجرا میشوند.
سیستم snapshot به صورت تراکنشی کار میکند: چندین تغییر State در یک رویداد در یک تراکنش ترکیب میشوند که از recompositionهای متعدد جلوگیری میکند. این امر به ویژه در پردازش ژستها مهم است: در یک حرکت، چندین شیء State تغییر میکند، اما Compose فقط یک recomposition اجرا میکند.
@Composable
fun StateExample() {
var text by remember { mutableStateOf("Hello") }
var isVisible by remember { mutableStateOf(true) }
Column {
Text(text) // وابستگی به text را ثبت میکند
if (isVisible) { // وابستگی به isVisible را ثبت میکند
TextField(value = text, onValueChange = { text = it })
}
Button(onClick = { isVisible = !isVisible }) {
Text(if (isVisible) "مخفی کردن" else "نمایش")
}
}
}
تغییر text منجر به recomposition فقط Column، Text و TextField میشود. Button و شرط isVisible بدون تغییر میمانند. چنین ایزولهبودن recomposition مزیت کلیدی Compose نسبت به سیستمهایی است که کل صفحه را دوباره ترسیم میکنند. هر تابع Composable فقط آن اشیاء State را که مستقیماً میخواند، دنبال میکند.
Composition (ترکیب) و Recomposition (بازترکیب) — دو حالت مختلف اجرای توابع Composable هستند. Composition یک بار هنگام ایجاد صفحه رخ میدهد: سیستم تمام توابع Composable را با مقادیر اولیه اجرا کرده و درخت UI اولیه را میسازد. Recomposition چندین بار هنگام تغییر دادهها رخ میدهد: سیستم فقط توابعی را که به وضعیت تغییر یافته وابسته هستند، راهاندازی مجدد میکند.
حالت Composition تمام گرههای درخت را فعال میکند، برای هر تابع slot اختصاص میدهد، تمام فرزندان را ثبت میکند. Recomposition به صورت انتخابی کار میکند: Compose مقادیر جدید و قدیم پارامترهای هر تابع را مقایسه میکند و اگر تغییر نکرده باشند — تابع اجرا نمیشود (skipping).
Composition و Recomposition از نظر هزینه متفاوت هستند. اولین Composition گرانتر است، زیرا نیاز به ساخت کامل درخت و اختصاص slotها دارد. Recomposition ارزانتر است، به خصوص اگر اکثر توابع stable باشند — پارامترهای آنها با equals مقایسه میشوند و Compose فراخوانی آنها را رد میکند. برای حداکثر کارایی، باید تلاش کرد تا حد امکان توابع کمتری تحت تأثیر recomposition قرار گیرند.
| ویژگی | Composition | Recomposition |
|---|---|---|
| زمان وقوع | یک بار، در اولین نمایش | چندین بار، هنگام تغییر دادهها |
| حجم | کل درخت | فقط توابع تغییر یافته |
| مقایسه پارامترها | انجام نمیشود | برای skipping انجام میشود |
| ایجاد slotها | بله، تمام slotها ایجاد میشوند | فقط برای گرههای جدید |
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 یک recomposition جدید برنامهریزی میکند که پس از اتمام جاری اجرا میشود. حلقه بینهایت رخ نمیدهد: Compose تضمین میکند که هر recomposition در یک تراکنش جداگانه از سیستم snapshot اجرا میشود.
در دستگاههای مدرن، Composition صفحه با 50–100 تابع Composable حدود 1–5 ms طول میکشد. Google توصیه میکند برای فریم 60fps در 16 ms بگنجید. اگر Composition از این حد فراتر رفت، از LazyColumn استفاده کنید یا صفحه را به توابع کوچکتر تقسیم کنید.
اجرای دستی مستقیم Composition ممکن نیست — به طور خودکار توسط Composer مدیریت میشود. با این حال، میتوان با تغییر State یا فراخوانی invalidate() در کامپوزیت ریشه، اگر به CompositionContext دسترسی وجود دارد، recomposition را اجباری برنامهریزی کرد.
سلسلهمراتب View — درختی تغییرناپذیر از اشیاء Java است که یک بار ایجاد میشود. Composition — درختی مجازی است که با هر تغییر داده بازسازی میشود. View وضعیت خود را در متغیرهای نمونه ذخیره میکند، Composition — در slotهای متصل به موقعیت فراخوانی تابع.
اگر تابع Composable دیگر فراخوانی نشود (مثلاً شرط if false شود)، Composition گره آن را حذف کرده و پاکسازی DisposableEffect را فراخوانی میکند. هنگام ظهور مجدد (if دوباره true) یک گره جدید ایجاد میشود — گره قدیمی بازیابی نمیشود.
خلاصه
ما یک اپلیکیشن موبایل به صورت کلید در دست توسعه خواهیم داد
IT Sectr از سال 2017 برنامههای iOS و Android را برای استارتاپها و کسبوکارها ایجاد میکند. ما به شما مشاوره میدهیم و بهترین راهحل را پیشنهاد خواهیم کرد.
همچنین بخوانید