وCompositionLocal
هي أداة لتمرير البيانات بشكل ضمني إلى "المقطوعة الموسيقية". في هذه الصفحة، ستعرِّفك على CompositionLocal
بالتفصيل، وكيفية إنشاء CompositionLocal
خاص بك، ومعرفة ما إذا كان CompositionLocal
هو الحلّ المناسب لحالتك.
التعريف ببرنامج CompositionLocal
في Compose، تتدفق البيانات للأسفل عادةً من خلال شجرة واجهة المستخدم كمَعلمات لكل دالة قابلة للتجميع. ويؤدي ذلك إلى توضيح التبعيات للعنصر القابل للتجميع. ومع ذلك، قد يكون هذا الأمر صعبًا بالنسبة إلى البيانات التي يتم استخدامها بكثرة وعلى نطاق واسع، مثل الألوان أو أنماط الكتابة. اطّلِع على المثال التالي:
@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 ) }
لدعم عدم الحاجة إلى تمرير الألوان كتبعية صريحة للمَعلمة إلى معظم العناصر القابلة للإنشاء، توفّر ميزة "إنشاء" CompositionLocal
، ما يسمح لك بإنشاء كائنات مُسمّاة على مستوى الشجرة ويمكن استخدامها كطريقة ضمنية لتدفق البيانات عبر شجرة واجهة المستخدم.
عادةً ما يتم توفير عناصر CompositionLocal
مع قيمة في عقدة معينة من شجرة واجهة المستخدم. ويمكن استخدام هذه القيمة من قِبل العناصر الفرعية القابلة للتجميع بدون
تحديد CompositionLocal
كمَعلمة في الدالة القابلة للتجميع.
CompositionLocal
هو ما يستخدمه مظهر Material في الخلفية.
وMaterialTheme
هو عنصر يوفّر ثلاث مثيلات من CompositionLocal
: colorScheme
وtypography
وshapes
، ما يسمح لك باستردادها لاحقًا في أي جزء تابع من المقطوعة الموسيقية.
وعلى وجه التحديد، هذه هي السمات 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
في جزء من التركيب حتى تتمكّن
من تقديم قيم مختلفة في مستويات مختلفة من الشجرة. تتوافق قيمة current
لعنصر CompositionLocal
مع أقرب قيمة يوفّرها أحد
العناصر الأصل في هذا الجزء من التركيب.
لتوفير قيمة جديدة لمفتاح CompositionLocal
، استخدِم دالة
CompositionLocalProvider
ودالة التوسّط provides
التي تربط مفتاح CompositionLocal
بمفتاح value
. ستحصل
دالة lambda content
لـ CompositionLocalProvider
على القيمة المقدَّمة عند الوصول إلى السمة current
في CompositionLocal
. عند توفير قيمة جديدة، تعيد ميزة Compose إنشاء أجزاء من المقطوعة الموسيقية تتضمّن CompositionLocal
.
على سبيل المثال، يحتوي LocalContentColor
CompositionLocal
على لون المحتوى المفضّل المستخدَم للنص والرسومات لضمان تباينه مع لون الخلفية الحالي. في المثال التالي، يتم استخدام 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
في المثال الأخير، تم استخدام مثيلات CompositionLocal
داخليًا بواسطة
المواد القابلة للإنشاء. للوصول إلى القيمة الحالية لسمة 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
هي أداة لتمرير البيانات إلى أسفل المقطوعة الموسيقية ضمنيًا.
إشارة رئيسية أخرى لاستخدام CompositionLocal
هي عندما تكون المَعلمة
شاملة ويجب ألا تكون الطبقات الوسيطة للتنفيذ على دراية
بوجودها، لأنّ جعل هذه الطبقات الوسيطة على دراية بها سيحدّ من
فائدة العنصر القابل للتجميع. على سبيل المثال، يتم تنفيذ طلبات البحث عن أذونات Android
من خلال CompositionLocal
في الخلفية. يمكن أن تضيف أداة اختيار الوسائط القابلة للتركيب
وظائف جديدة للوصول إلى المحتوى المحمي بإذن على
الجهاز بدون تغيير واجهة برمجة التطبيقات الخاصة بها ومطالبة مُرسِلي طلبات أداة اختيار الوسائط بالانتباه
إلى هذا السياق المُضاف المستخدَم من البيئة.
ومع ذلك، لا يُعدّ CompositionLocal
الحل الأفضل دائمًا. ننصح بعدم إساءة استخدام CompositionLocal
لأنّ ذلك قد يؤدي إلى بعض النتائج السلبية:
يصعّب CompositionLocal
فهم سلوك المحتوى القابل للإنشاء. وبما أنّها تُنشئ تبعيات ضمنية، على مُستدعي العناصر القابلة للتجميع التي تستخدمها
التأكّد من توفّر قيمة لكل CompositionLocal
.
فضلاً عن ذلك، قد لا يكون هناك مصدر واضح لهذه التبعية، إذ إنّها قد تتغير في أي جزء من المقطوعة الموسيقية. وبالتالي، قد يكون تصحيح الأخطاء في التطبيق عند حدوث مشكلة أكثر صعوبة حيث تحتاج إلى الانتقال للأعلى
في المقطوعة الموسيقية لمعرفة مكان توفير قيمة current
. توفّر أدوات مثل العثور على
مواقع الاستخدام في IDE أو أداة فحص تنسيق الإنشاء معلومات كافية للتخفيف من هذه المشكلة.
تحديد ما إذا كنت تريد استخدام CompositionLocal
هناك شروط معيّنة يمكن أن تجعل CompositionLocal
حلًا جيدًا
لحالة الاستخدام:
يجب أن تحتوي سمة CompositionLocal
على قيمة تلقائية جيدة. إذا لم تكن هناك قيمة CompositionLocal
تلقائية، يجب أن تضمن أنّه من الصعب جدًا على المطوّر أن يقع في موقف لا يتم فيه تقديم قيمة لسمة CompositionLocal
.
يمكن أن يؤدي عدم توفير قيمة تلقائية إلى حدوث مشاكل أو شعور بالإحباط عند إنشاء اختبارات أو معاينة عنصر قابل للإنشاء يستخدم قيمة CompositionLocal
التي سيتطلب دائمًا
تقديمها بشكل صريح.
تجنَّب CompositionLocal
للمفاهيم التي لا يتم اعتبارها على مستوى الشجرة أو
التدرّج الهرمي الفرعي. يكون استخدام CompositionLocal
منطقيًا عندما يمكن
استخدامه من قِبل أي نسل، وليس من قِبل بضعة أنسال.
إذا كانت حالة الاستخدام لديك لا تستوفي هذه المتطلبات، راجِع قسم
البدائل التي يجب مراعاتها قبل إنشاء
CompositionLocal
.
من الأمثلة على الممارسات غير الصحيحة إنشاء CompositionLocal
يحتوي على
ViewModel
شاشة معيّنة لكي تتمكّن جميع العناصر القابلة للتجميع في تلك الشاشة من
الحصول على مرجع إلى ViewModel
لتنفيذ بعض المنطق. هذه ممارسة سيئة
لأنّ بعض العناصر القابلة للتجميع ضمن شجرة واجهة مستخدِم معيّنة لا تحتاج إلى معرفة
ViewModel
. من الممارسات الجيدة عدم تمرير المعلومات التي يحتاجونها إلى العناصر القابلة للإنشاء سوى باتّباع النمط الذي تتدفق فيه الحالة وتتدفق الأحداث. سيؤدي هذا النهج إلى جعل العناصر القابلة للتجميع أكثر
قابلية لإعادة الاستخدام وأسهل في الاختبار.
إنشاء CompositionLocal
تتوفّر واجهتَا برمجة تطبيقات لإنشاء CompositionLocal
:
compositionLocalOf
: يؤدي تغيير القيمة المقدَّمة أثناء إعادة التركيب إلى إلغاء صلاحية المحتوى الذي يقرأ قيمةcurrent
فقط.staticCompositionLocalOf
: على عكسcompositionLocalOf
، لا تتتبّع أداة Compose عمليات قراءةstaticCompositionLocalOf
. يؤدي تغيير القيمة إلى إعادة تركيبcontent
لامدا بالكامل حيث يتم تقديمCompositionLocal
، بدلاً من الأماكن التي تتم فيها قراءة قيمةcurrent
في التركيب فقط.
إذا كان من غير المرجّح أن تتغيّر القيمة المقدَّمة لسمة 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
تربط الدالة القابلة للتجميع 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 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") } }
يمكن أن يكون هذا النهج مناسبًا بشكل أفضل لبعض حالات الاستخدام، لأنّه يفصل بين العنصر الفرعي وعناصره الأصلية المباشرة. تميل العناصر القابلة للإنشاء إلى أن تكون أكثر تعقيدًا بدلاً من وجود عناصر قابلة للإنشاء أكثر مرونةً على مستوى أقل.
يمكن أيضًا استخدام دالة lambdas في محتوى @Composable
بالطريقة نفسها للحصول على المزايا نفسها:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
أفلام مُقترَحة لك
- ملاحظة: يتم عرض نص الرابط عندما تكون لغة JavaScript غير مفعّلة.
- تحليل بنية المظهر في ميزة "الإنشاء"
- استخدام "طرق العرض" في ميزة "الإنشاء"
- Kotlin لـ Jetpack Compose