CompositionLocal ابزاری برای انتقال دادهها از طریق Composition به صورت ضمنی است. در این صفحه، شما با جزئیات بیشتری یاد خواهید گرفت که CompositionLocal چیست، چگونه CompositionLocal خود را ایجاد کنید و بدانید که آیا CompositionLocal راه حل خوبی برای مورد استفاده شما است یا خیر.
مقدمهای بر CompositionLocal
معمولاً در Compose، دادهها از طریق درخت رابط کاربری به عنوان پارامترهایی به هر تابع composable جریان مییابند . این امر وابستگیهای یک 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 ) }
برای پشتیبانی از عدم نیاز به ارسال رنگها به عنوان یک وابستگی پارامتر صریح به اکثر composableها، Compose CompositionLocal ارائه میدهد که به شما امکان میدهد اشیاء نامگذاری شده با دامنه درختی ایجاد کنید که میتوانند به عنوان روشی ضمنی برای جریان دادهها از طریق درخت UI استفاده شوند.
عناصر CompositionLocal معمولاً در یک گره خاص از درخت رابط کاربری مقداری دارند. آن مقدار میتواند توسط فرزندان composable آن بدون تعریف 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 و تابع میانوند 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 . در مثال آخر، نمونههای CompositionLocal به صورت داخلی توسط Composableهای Material استفاده شدند. برای دسترسی به مقدار فعلی یک CompositionLocal ، از ویژگی current آن استفاده کنید. در مثال زیر، مقدار Context فعلی LocalContext CompositionLocal که معمولاً در برنامههای اندروید استفاده میشود، برای قالببندی متن استفاده میشود:
@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 زمانی است که پارامتر متقاطع است و لایههای میانی پیادهسازی نباید از وجود آن آگاه باشند، زیرا آگاه کردن آن لایههای میانی، کاربرد composable را محدود میکند. به عنوان مثال، پرسوجو برای مجوزهای اندروید توسط CompositionLocal در زیر کاپوت ارائه میشود. یک media picker composable میتواند قابلیتهای جدیدی را برای دسترسی به محتوای دارای مجوز محافظتشده در دستگاه بدون تغییر API آن و نیاز به فراخوانیکنندگان media picker برای آگاهی از این زمینه اضافه شده استفاده شده از محیط، اضافه کند.
با این حال، CompositionLocal همیشه بهترین راه حل نیست. ما استفاده بیش از حد CompositionLocal را توصیه نمیکنیم زیرا دارای برخی معایب است:
CompositionLocal استدلال رفتار یک composable را دشوارتر میکند . از آنجایی که آنها وابستگیهای ضمنی ایجاد میکنند، فراخوانیکنندههای composableهایی که از آنها استفاده میکنند باید مطمئن شوند که یک مقدار برای هر CompositionLocal برآورده میشود.
علاوه بر این، ممکن است هیچ منبع حقیقت روشنی برای این وابستگی وجود نداشته باشد زیرا میتواند در هر بخشی از کامپوزیشن تغییر کند. بنابراین، اشکالزدایی برنامه هنگام بروز مشکل میتواند چالش برانگیزتر باشد زیرا باید در کامپوزیشن به سمت بالا حرکت کنید تا ببینید مقدار current در کجا ارائه شده است. ابزارهایی مانند Find usages در IDE یا Compose layout inspector اطلاعات کافی برای کاهش این مشکل ارائه میدهند.
تصمیم بگیرید که آیا از CompositionLocal استفاده کنید یا خیر
شرایط خاصی وجود دارد که میتواند CompositionLocal به یک راهحل خوب برای مورد استفاده شما تبدیل کند:
یک CompositionLocal باید یک مقدار پیشفرض خوب داشته باشد . اگر مقدار پیشفرضی وجود نداشته باشد، باید تضمین کنید که برای یک توسعهدهنده بسیار دشوار است که در موقعیتی قرار گیرد که مقداری برای CompositionLocal ارائه نشده باشد. عدم ارائه مقدار پیشفرض میتواند هنگام ایجاد تستها یا پیشنمایش یک composable که از آن استفاده میکند، باعث ایجاد مشکلات و ناامیدی شود. CompositionLocal همیشه نیاز به ارائه صریح آن دارد.
از CompositionLocal برای مفاهیمی که به عنوان tree-scope یا sub-hierarchy-scope در نظر گرفته نمیشوند، اجتناب کنید . یک CompositionLocal زمانی منطقی است که بتواند به طور بالقوه توسط هر فرزندی مورد استفاده قرار گیرد، نه توسط تعداد کمی از آنها.
اگر مورد استفاده شما این الزامات را برآورده نمیکند، قبل از ایجاد CompositionLocal بخش «جایگزینهایی که باید در نظر گرفته شوند» را بررسی کنید.
یک نمونه از یک رویه نامناسب، ایجاد یک CompositionLocal است که ViewModel یک صفحه نمایش خاص را در خود نگه میدارد، به طوری که همه composableهای موجود در آن صفحه بتوانند برای انجام برخی منطقها، به ViewModel ارجاع دهند. این یک رویه نامناسب است زیرا همه composableهای زیر یک درخت UI خاص نیازی به دانستن ViewModel ندارند. رویه مناسب این است که فقط اطلاعاتی را که composableها نیاز دارند، با پیروی از الگویی که حالت به پایین و رویدادها به بالا جریان مییابد، به آنها منتقل کنید. این رویکرد، composableهای شما را قابل استفاده مجددتر و آزمایش آنها را آسانتر میکند.
ایجاد یک CompositionLocal
دو API برای ایجاد یک CompositionLocal وجود دارد:
compositionLocalOf: تغییر مقدار ارائه شده در طول recomposition، فقط محتوایی را که مقدارcurrentآن را میخواند، نامعتبر میکند.staticCompositionLocalOf: برخلافcompositionLocalOf، خواندنهای یکstaticCompositionLocalOfتوسط Compose ردیابی نمیشوند. تغییر مقدار باعث میشود کلcontentلامبدا کهCompositionLocalدر آن ارائه شده است، به جای فقط مکانهایی که مقدارcurrentدر Composition خوانده میشود، دوباره ترکیب شود.
اگر مقدار ارائه شده به CompositionLocal به احتمال زیاد تغییر نخواهد کرد یا هرگز تغییر نخواهد کرد، از staticCompositionLocalOf برای بهبود عملکرد استفاده کنید.
برای مثال، سیستم طراحی یک برنامه ممکن است در نحوهی ارتقای کامپوننتهای ترکیبی با استفاده از سایه برای کامپوننت رابط کاربری، خودرأی باشد. از آنجایی که ارتقای مختلف برای برنامه باید در سراسر درخت رابط کاربری منتشر شود، ما از یک 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 ارائه دهید
Composable CompositionLocalProvider مقادیر را به نمونههای CompositionLocal برای سلسله مراتب داده شده متصل میکند . برای ارائه یک مقدار جدید به یک CompositionLocal ، از تابع میانوند provides استفاده کنید که یک کلید 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ها نیاز دارند، به آنها منتقل کنید . برای تشویق جداسازی و استفاده مجدد از composableها، هر 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، استفاده از وارونگی کنترل است. به جای اینکه فرزند، وابستگی را برای اجرای برخی منطقها دریافت کند، والد این کار را انجام میدهد.
به مثال زیر توجه کنید که در آن یک فرزند باید درخواست بارگذاری برخی دادهها را آغاز کند:
@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") } }
این رویکرد میتواند برای برخی موارد استفاده مناسبتر باشد، زیرا فرزند را از اجداد بلافصلش جدا میکند . ترکیبپذیرهای اجداد به نفع داشتن ترکیبپذیرهای سطح پایینتر و انعطافپذیرتر، تمایل به پیچیدهتر شدن دارند.
به طور مشابه، میتوان از لامبداهای content @Composable به همان روش برای دستیابی به مزایای مشابه استفاده کرد:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
برای شما توصیه میشود
- توجه: متن لینک زمانی نمایش داده میشود که جاوا اسکریپت غیرفعال باشد.
- آناتومی یک تم در Compose
- استفاده از Viewها در Compose
- کاتلین برای جتپک کامپوز