CompositionLocal ابزاری برای انتقال داده ها از طریق Composition به طور ضمنی است. در این صفحه، با جزئیات بیشتری یاد خواهید گرفت که CompositionLocal چیست، چگونه CompositionLocal خود را ایجاد کنید، و بدانید که آیا CompositionLocal راه حل خوبی برای موارد استفاده شما است یا خیر.
مقدمه ای بر CompositionLocal
معمولاً در Compose، داده ها از طریق درخت UI به عنوان پارامترهای هر تابع قابل ترکیب به پایین جریان می یابد . این امر وابستگی های یک composable را واضح می کند. با این حال، این می تواند برای داده هایی که بسیار زیاد و به طور گسترده مورد استفاده قرار می گیرند، مانند رنگ ها یا سبک های نوع، دست و پا گیر باشد. مثال زیر را ببینید:
@Composable fun MyApp() { // Theme information tends to be defined near the root of the application val colors = colors() } // Some composable deep in the hierarchy @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, color = colors.onPrimary // ← need to access colors here ) }
برای پشتیبانی از عدم نیاز به انتقال رنگها بهعنوان یک وابستگی صریح پارامتر به بیشتر ترکیبپذیرها، Compose CompositionLocal را ارائه میدهد که به شما امکان میدهد اشیاء نامگذاری شده با دامنه درختی ایجاد کنید که میتواند به عنوان یک روش ضمنی برای داشتن جریان داده از طریق درخت UI استفاده شود.
عناصر CompositionLocal معمولاً با یک مقدار در گره خاصی از درخت UI ارائه می شوند. این مقدار می تواند توسط نوادگان قابل ترکیب آن بدون اعلام CompositionLocal به عنوان یک پارامتر در تابع composable استفاده شود.
CompositionLocal چیزی است که تم Material در زیر هود استفاده می کند. MaterialTheme شیای است که سه نمونه CompositionLocal را ارائه میکند: colorScheme ، typography و shapes ، که به شما امکان میدهد بعداً آنها را در هر قسمتی از Composition بازیابی کنید. به طور خاص، اینها ویژگیهای LocalColorScheme ، LocalShapes و LocalTypography هستند که میتوانید از طریق ویژگیهای MaterialTheme colorScheme ، shapes و typography به آنها دسترسی داشته باشید.
@Composable fun MyApp() { // Provides a Theme whose values are propagated down its `content` MaterialTheme { // New values for colorScheme, typography, and shapes are available // in MaterialTheme's content lambda. // ... content here ... } } // Some composable deep in the hierarchy of MaterialTheme @Composable fun SomeTextLabel(labelText: String) { Text( text = labelText, // `primary` is obtained from MaterialTheme's // LocalColors CompositionLocal color = MaterialTheme.colorScheme.primary ) }
یک نمونه CompositionLocal به بخشی از Composition اختصاص داده شده است تا بتوانید مقادیر مختلفی را در سطوح مختلف درخت ارائه دهید. مقدار current یک CompositionLocal با نزدیکترین مقدار ارائه شده توسط یک اجداد در آن قسمت از Composition مطابقت دارد.
برای ارائه یک مقدار جدید به CompositionLocal ، از CompositionLocalProvider و تابع infix provides که یک کلید CompositionLocal به یک value مرتبط می کند، استفاده کنید. لامبدای content CompositionLocalProvider هنگام دسترسی به ویژگی current CompositionLocal مقدار ارائه شده را دریافت می کند. وقتی مقدار جدیدی ارائه میشود، Compose قسمتهایی از Composition را که CompositionLocal میخوانند، دوباره ترکیب میکند.
به عنوان مثالی از این، LocalContentColor CompositionLocal حاوی رنگ محتوای ترجیحی مورد استفاده برای متن و نماد نگاری است تا از تضاد آن با رنگ پسزمینه فعلی اطمینان حاصل کند. در مثال زیر، CompositionLocalProvider برای ارائه مقادیر مختلف برای بخشهای مختلف Composition استفاده میشود.
@Composable fun CompositionLocalExample() { MaterialTheme { // Surface provides contentColorFor(MaterialTheme.colorScheme.surface) by default // This is to automatically make text and other content contrast to the background // correctly. Surface { Column { Text("Uses Surface's provided content color") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.primary) { Text("Primary color provided by LocalContentColor") Text("This Text also uses primary as textColor") CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.error) { DescendantExample() } } } } } } @Composable fun DescendantExample() { // CompositionLocalProviders also work across composable functions Text("This Text uses the error color now") }

CompositionLocalExample composable. در آخرین مثال، نمونههای CompositionLocal به صورت داخلی توسط مواد composable استفاده میشوند. برای دسترسی به مقدار فعلی یک CompositionLocal ، از ویژگی current آن استفاده کنید. در مثال زیر، مقدار Context فعلی LocalContext CompositionLocal که معمولاً در برنامههای Android استفاده میشود، برای قالببندی متن استفاده میشود:
@Composable fun FruitText(fruitSize: Int) { // Get `resources` from the current value of LocalContext val resources = LocalContext.current.resources val fruitText = remember(resources, fruitSize) { resources.getQuantityString(R.plurals.fruit_title, fruitSize) } Text(text = fruitText) }
CompositionLocal خود را ایجاد کنید
CompositionLocal ابزاری برای انتقال داده ها از طریق Composition به طور ضمنی است.
یکی دیگر از سیگنالهای کلیدی برای استفاده از CompositionLocal زمانی است که پارامتر مقطعی است و لایههای میانی پیادهسازی نباید از وجود آن آگاه باشند، زیرا آگاه کردن آن لایههای میانی، کاربرد ترکیبپذیر را محدود میکند. به عنوان مثال، درخواست مجوزهای Android توسط CompositionLocal در زیر هود انجام می شود. یک انتخابگر رسانه قابل ترکیب میتواند عملکرد جدیدی را برای دسترسی به محتوای محافظتشده با مجوز در دستگاه بدون تغییر API آن اضافه کند و از تماسگیرندگان انتخابگر رسانه بخواهد از این زمینه اضافه استفاده شده از محیط آگاه باشند.
با این حال، CompositionLocal همیشه بهترین راه حل نیست. ما استفاده بیش از حد از CompositionLocal منع می کنیم زیرا دارای برخی از جنبه های منفی است:
CompositionLocal رفتار یک composable را برای استدلال سخت تر می کند . همانطور که وابستگی های ضمنی ایجاد می کنند، فراخواننده های composable که از آنها استفاده می کنند باید مطمئن شوند که یک مقدار برای هر CompositionLocal برآورده شده است.
علاوه بر این، ممکن است هیچ منبع روشنی از حقیقت برای این وابستگی وجود نداشته باشد زیرا می تواند در هر بخشی از ترکیب جهش پیدا کند. بنابراین، اشکالزدایی برنامه در هنگام بروز مشکل میتواند چالشبرانگیزتر باشد ، زیرا باید در Composition حرکت کنید تا ببینید مقدار current کجا ارائه شده است. ابزارهایی مانند Find usages در IDE یا Compose layout inspector اطلاعات کافی برای کاهش این مشکل ارائه می دهند.
تصمیم بگیرید که از CompositionLocal استفاده کنید یا خیر
شرایط خاصی وجود دارد که می تواند CompositionLocal به یک راه حل خوب برای موارد استفاده شما تبدیل کند:
یک CompositionLocal باید مقدار پیش فرض خوبی داشته باشد . اگر مقدار پیشفرض وجود ندارد، باید تضمین کنید که برای یک توسعهدهنده بسیار دشوار است که در موقعیتی قرار بگیرد که در آن مقداری برای CompositionLocal ارائه نشده باشد. عدم ارائه یک مقدار پیشفرض میتواند باعث ایجاد مشکل و ناامیدی در هنگام ایجاد آزمایش یا پیشنمایش یک composable شود که از آن CompositionLocal استفاده میکند، همیشه باید به طور صریح ارائه شود.
از CompositionLocal برای مفاهیمی که به عنوان محدوده درختی یا زیر سلسله مراتبی تصور نمی شوند، اجتناب کنید . یک CompositionLocal زمانی معنا پیدا میکند که بتوان آن را به طور بالقوه توسط هر نسلی استفاده کرد، نه تعداد کمی از آنها.
اگر مورد استفاده شما این الزامات را برآورده نمی کند، قبل از ایجاد CompositionLocal ، بخش Alternatives to consider را بررسی کنید.
یک مثال از یک عمل بد، ایجاد یک CompositionLocal است که ViewModel یک صفحه خاص را نگه میدارد، به طوری که همه اجزای سازنده در آن صفحه میتوانند برای اجرای منطقی به ViewModel ارجاع دهند. این یک روش بد است زیرا همه اجزای سازنده زیر یک درخت UI خاص نیازی به دانستن یک ViewModel ندارند. روش خوب این است که فقط اطلاعاتی را که آنها به ترکیبپذیرها نیاز دارند، با پیروی از الگویی که حالت پایین میآید و رویدادها به سمت بالا میروند، منتقل میکنند. این رویکرد باعث میشود که کامپوزیشنهای شما قابل استفاده مجدد و آزمایش آسانتر باشند.
یک CompositionLocal ایجاد کنید
دو API برای ایجاد CompositionLocal وجود دارد:
compositionLocalOf: تغییر مقدار ارائه شده در حین ترکیب مجدد، فقط محتوایی را که مقدارcurrentآن را میخواند، بی اعتبار میکند.staticCompositionLocalOf: برخلافcompositionLocalOf، خواندنstaticCompositionLocalOfتوسط Compose ردیابی نمیشود. تغییر مقدار باعث می شود که کلcontentلامبدا که در آنCompositionLocalارائه شده است، به جای مکان هایی که مقدارcurrentدر Composition خوانده می شود، دوباره ترکیب شود.
اگر مقدار ارائه شده به CompositionLocal بسیار بعید است که تغییر کند یا هرگز تغییر نخواهد کرد، از staticCompositionLocalOf برای دریافت مزایای عملکرد استفاده کنید.
به عنوان مثال، سیستم طراحی یک برنامه ممکن است به روشی که ترکیبپذیرها با استفاده از یک سایه برای مؤلفه UI بالا میروند، مورد ارزیابی قرار گیرد. از آنجایی که ارتفاعات مختلف برنامه باید در سراسر درخت رابط کاربری منتشر شود، از CompositionLocal استفاده میکنیم. از آنجایی که مقدار CompositionLocal به صورت مشروط بر اساس موضوع سیستم مشتق می شود، ما از compositionLocalOf API استفاده می کنیم:
// LocalElevations.kt file data class Elevations(val card: Dp = 0.dp, val default: Dp = 0.dp) // Define a CompositionLocal global object with a default // This instance can be accessed by all composables in the app val LocalElevations = compositionLocalOf { Elevations() }
مقادیری را به CompositionLocal ارائه دهید
Composible CompositionLocalProvider مقادیر را به نمونه های CompositionLocal برای سلسله مراتب داده شده متصل می کند . برای ارائه یک مقدار جدید به CompositionLocal ، از تابع provides infix استفاده کنید که یک کلید CompositionLocal را به صورت زیر به یک value مرتبط می کند:
// MyActivity.kt file class MyActivity : ComponentActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContent { // Calculate elevations based on the system theme val elevations = if (isSystemInDarkTheme()) { Elevations(card = 1.dp, default = 1.dp) } else { Elevations(card = 0.dp, default = 0.dp) } // Bind elevation as the value for LocalElevations CompositionLocalProvider(LocalElevations provides elevations) { // ... Content goes here ... // This part of Composition will see the `elevations` instance // when accessing LocalElevations.current } } } }
مصرف CompositionLocal
CompositionLocal.current مقدار ارائه شده توسط نزدیکترین CompositionLocalProvider را برمی گرداند که مقداری را برای آن CompositionLocal ارائه می کند:
@Composable fun SomeComposable() { // Access the globally defined LocalElevations variable to get the // current Elevations in this part of the Composition MyCard(elevation = LocalElevations.current.card) { // Content } }
جایگزین هایی که باید در نظر گرفته شوند
CompositionLocal ممکن است راه حلی بیش از حد برای برخی موارد استفاده باشد. اگر مورد استفاده شما معیارهای مشخص شده در بخش تصمیم گیری برای استفاده از CompositionLocal را برآورده نمی کند، احتمالاً راه حل دیگری برای مورد استفاده شما مناسب تر است.
پارامترهای صریح را پاس کنید
صریح بودن در مورد وابستگی های composable یک عادت خوب است. ما توصیه می کنیم که قطعات کامپوزیشن را فقط آنچه را که نیاز دارند ارسال کنید . برای تشویق جداسازی و استفاده مجدد از مواد ترکیبپذیر، هر ترکیبپذیر باید حداقل اطلاعات ممکن را در خود داشته باشد.
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel.data) } // Don't pass the whole object! Just what the descendant needs. // Also, don't pass the ViewModel as an implicit dependency using // a CompositionLocal. @Composable fun MyDescendant(myViewModel: MyViewModel) { /* ... */ } // Pass only what the descendant needs @Composable fun MyDescendant(data: DataToDisplay) { // Display data }
وارونگی کنترل
راه دیگر برای جلوگیری از انتقال وابستگی های غیر ضروری به یک کامپوزیشن، استفاده از وارونگی کنترل است. به جای اینکه فرزند وابستگی برای اجرای برخی منطق داشته باشد، والد این کار را به جای آن انجام می دهد.
مثال زیر را ببینید که در آن یک نسل باید درخواست بارگیری برخی از داده ها را راه اندازی کند:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... MyDescendant(myViewModel) } @Composable fun MyDescendant(myViewModel: MyViewModel) { Button(onClick = { myViewModel.loadData() }) { Text("Load data") } }
بسته به مورد، MyDescendant ممکن است مسئولیت زیادی داشته باشد. همچنین، انتقال MyViewModel به عنوان یک وابستگی باعث می شود MyDescendant کمتر قابل استفاده مجدد باشد زیرا آنها اکنون با هم جفت شده اند. جایگزینی را در نظر بگیرید که وابستگی را به نسل منتقل نمی کند و از وارونگی اصول کنترل استفاده می کند که جد را مسئول اجرای منطق می کند:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusableLoadDataButton( onLoadClick = { myViewModel.loadData() } ) } @Composable fun ReusableLoadDataButton(onLoadClick: () -> Unit) { Button(onClick = onLoadClick) { Text("Load data") } }
این رویکرد می تواند برای برخی موارد استفاده بهتر مناسب باشد زیرا کودک را از اجداد نزدیکش جدا می کند . ترکیبپذیرهای اجدادی به نفع داشتن ترکیبپذیرهای سطح پایینتر انعطافپذیرتر، پیچیدهتر میشوند.
به طور مشابه، @Composable content lambdas را می توان به همان روش استفاده کرد تا از مزایای یکسانی برخوردار شود:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- آناتومی یک تم در Compose
- استفاده از Views در Compose
- Kotlin برای Jetpack Compose