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") }
شکل 1. پیش نمایش 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
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") }
شکل 1. پیش نمایش 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
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") }
شکل 1. پیش نمایش 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
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
مقدار ارائه شده را دریافت می کند. هنگامی که یک مقدار جدید ارائه می شود ، آهنگسازی بخش هایی از ترکیب را که CompositionLocal
را می خوانند ، توصیه می کند.
به عنوان نمونه ای از این ، CompositionLocal
LocalContentColor
حاوی رنگ محتوای ترجیحی است که برای متن و نمادنگاری استفاده می شود تا اطمینان حاصل شود که در مقابل رنگ پس زمینه فعلی تضاد دارد. در مثال زیر ، CompositionLocalProvider
برای ارائه مقادیر مختلف برای قسمت های مختلف ترکیب استفاده می شود.
@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") }
شکل 1. پیش نمایش CompositionLocalExample
Composable.
در مثال آخر ، نمونه های CompositionLocal
در داخل توسط ترکیبات مواد مورد استفاده قرار گرفتند. برای دسترسی به مقدار فعلی یک CompositionLocal
، از ویژگی current
آن استفاده کنید. در مثال زیر ، مقدار Context
فعلی CompositionLocal
LocalContext
که معمولاً در برنامه های 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
ابزاری برای عبور داده ها از طریق ترکیب به طور ضمنی است.
یکی دیگر از سیگنال های اصلی برای استفاده از CompositionLocal
زمانی است که پارامتر متقاطع است و لایه های واسطه ای از اجرای آن نباید آگاه باشد که وجود دارد ، زیرا آگاهی از این لایه های میانی باعث محدود کردن کاربرد این ترکیب می شود. به عنوان مثال ، پرس و جو برای مجوزهای اندرویدی توسط یک CompositionLocal
در زیر کاپوت فراهم می شود. یک ترکیب انتخاب کننده رسانه می تواند عملکرد جدیدی را برای دسترسی به محتوای مجاز محافظت شده در دستگاه بدون تغییر API خود اضافه کند و به تماس گیرنده های انتخاب کننده رسانه نیاز داشته باشد تا از این زمینه اضافه شده که از محیط استفاده می شود ، آگاه باشند.
با این حال ، CompositionLocal
همیشه بهترین راه حل نیست. ما از CompositionLocal
بیش از حد استفاده می کنیم زیرا با برخی از نقاط ضعف همراه است:
CompositionLocal
رفتار یک ترکیب را سخت تر می کند . از آنجا که آنها وابستگی های ضمنی ایجاد می کنند ، تماس گیرندگان آهنگسازانی که از آنها استفاده می کنند ، باید اطمینان حاصل کنند که یک مقدار برای هر CompositionLocal
راضی است.
علاوه بر این ، ممکن است هیچ منبع مشخصی برای این وابستگی وجود نداشته باشد زیرا می تواند در هر بخشی از ترکیب جهش یابد. بنابراین ، اشکال زدایی برنامه در هنگام بروز مشکل می تواند چالش برانگیزتر باشد زیرا شما نیاز به حرکت در ترکیب دارید تا ببینید که مقدار current
در کجا ارائه شده است. ابزارهایی از قبیل یافتن کاربردهای موجود در IDE یا بازرس طرح بندی ، اطلاعات کافی را برای کاهش این مسئله ارائه می دهند.
تصمیم گیری در مورد استفاده از CompositionLocal
شرایط خاصی وجود دارد که می تواند CompositionLocal
را به یک راه حل خوب برای مورد استفاده شما تبدیل کند:
CompositionLocal
باید مقدار پیش فرض خوبی داشته باشد . اگر هیچ مقدار پیش فرض وجود نداشته باشد ، باید تضمین کنید که ورود به یک توسعه دهنده بسیار دشوار است که بتواند مقداری برای CompositionLocal
فراهم شود. عدم ارائه مقدار پیش فرض می تواند هنگام ایجاد تست یا پیش نمایش یک ترکیب که از آن CompositionLocal
استفاده می کند ، باعث ایجاد مشکلات و ناامیدی شود.
از CompositionLocal
برای مفاهیمی که به عنوان درختچه ای از درخت یا زیر سلسله مراتب استفاده نمی شوند ، خودداری کنید . CompositionLocal
وقتی می تواند به طور بالقوه توسط هر نوادگان استفاده شود ، نه توسط تعداد معدودی از آنها ، معقول می شود.
اگر مورد استفاده شما این الزامات را برآورده نمی کند ، قبل از ایجاد یک CompositionLocal
گزینه های دیگری را برای در نظر گرفتن بخش بررسی کنید.
نمونه ای از یک عمل بد ، ایجاد یک CompositionLocal
است که ViewModel
یک صفحه نمایش خاص را در خود جای داده است به طوری که تمام آهنگسازان در آن صفحه می توانند برای انجام برخی منطق به ViewModel
مراجعه کنند. این یک عمل بد است زیرا همه آهنگهای زیر یک درخت UI خاص نیازی به دانستن در مورد ViewModel
ندارند. عمل خوب این است که فقط اطلاعاتی را که به آنها نیاز دارند به دنبال الگویی که دولت به پایین جریان می یابد و رویدادها جریان می یابند ، منتقل کنید. این رویکرد باعث می شود که ترکیبات شما قابل استفاده مجدد و آزمایش آسان تر شود.
ایجاد یک CompositionLocal
دو API برای ایجاد یک CompositionLocal
وجود دارد:
compositionLocalOf
: تغییر مقدار ارائه شده در هنگام بازپرداخت ، فقط محتوایی را که مقدارcurrent
آن را می خواند ، باطل می کند.staticCompositionLocalOf
: بر خلافcompositionLocalOf
، خوانده شده از یکstaticCompositionLocalOf
توسط آهنگسازی ردیابی نمی شود. تغییر مقدار باعث می شود کلیتcontent
لامبدا که در آنCompositionLocal
ترکیب شده است ، به جای مکانهایی که مقدارcurrent
در ترکیب خوانده می شود ، مجدداً مورد استفاده قرار گیرد.
اگر مقدار ارائه شده به CompositionLocal
بعید به نظر می رسد تغییر کند یا هرگز تغییر نخواهد کرد ، برای به دست آوردن مزایای عملکرد از staticCompositionLocalOf
استفاده کنید.
به عنوان مثال ، سیستم طراحی یک برنامه ممکن است در نحوه بالا بردن ترکیبات با استفاده از سایه برای مؤلفه UI نظر داده شود. از آنجا که ارتفاعات مختلف برنامه باید در سراسر درخت UI پخش شود ، ما از یک CompositionLocal
استفاده می کنیم. از آنجا که مقدار CompositionLocal
به صورت مشروط بر اساس موضوع سیستم مشتق شده است ، ما از API compositionLocalOf
استفاده می کنیم:
// 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
ترکیب ترکیب CompositionLocalProvider
مقادیر را به نمونه های CompositionLocal
برای سلسله مراتب داده شده متصل می کند . برای تهیه یک مقدار جدید به یک CompositionLocal
، از عملکرد Infix استفاده کنید که یک کلید CompositionLocal
را به یک value
به شرح زیر مرتبط provides
کند:
// 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
ممکن است یک راه حل بیش از حد برای برخی موارد استفاده باشد. اگر مورد استفاده شما معیارهای مشخص شده در تصمیم گیری در مورد استفاده از بخش ComposionLocal را برآورده نمی کند ، ممکن است راه حل دیگری برای مورد استفاده شما مناسب تر باشد.
پارامترهای صریح را عبور دهید
صریح بودن در مورد وابستگی های 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
Lambdas می تواند به همان روش برای به دست آوردن همان مزایا استفاده شود :
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
برای شما توصیه می شود
- توجه: وقتی جاوا اسکریپت خاموش است، متن پیوند نمایش داده می شود
- آناتومی یک موضوع در آهنگسازی
- با استفاده از نماها در آهنگسازی
- کوتلین برای آهنگسازی Jetpack