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 ) }
ज़्यादातर कंपोज़ेबल में, रंगों को पैरामीटर की डिपेंडेंसी के तौर पर पास करने की ज़रूरत नहीं होती. इसके लिए, Compose, 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
इंस्टेंस को कंपोज़िशन के किसी हिस्से के लिए स्कोप किया जाता है, ताकि ट्री के अलग-अलग लेवल पर अलग-अलग वैल्यू दी जा सकें. CompositionLocal
की current
वैल्यू, कंपोज़िशन के उस हिस्से में पूर्वज की ओर से दी गई सबसे नज़दीकी वैल्यू से मेल खाती है.
किसी CompositionLocal
को नई वैल्यू देने के लिए, CompositionLocalProvider
और इसके provides
इनफ़िक्स फ़ंक्शन का इस्तेमाल करें. यह फ़ंक्शन, CompositionLocal
कुंजी को value
से जोड़ता है. CompositionLocalProvider
के content
लैंबडा को, CompositionLocal
की current
प्रॉपर्टी को ऐक्सेस करते समय दी गई वैल्यू मिलेगी. नई वैल्यू दिए जाने पर, 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") }

CompositionLocalExample
कंपोज़ेबल की झलक.पिछले उदाहरण में, CompositionLocal
इंस्टेंस का इस्तेमाल Material कंपोज़ेबल ने इंटरनल तौर पर किया था. किसी CompositionLocal
की मौजूदा वैल्यू को ऐक्सेस करने के लिए, उसकी current
प्रॉपर्टी का इस्तेमाल करें. इस उदाहरण में, Android ऐप्लिकेशन में आम तौर पर इस्तेमाल होने वाले LocalContext
CompositionLocal
की मौजूदा Context
वैल्यू का इस्तेमाल, टेक्स्ट को फ़ॉर्मैट करने के लिए किया गया है:
@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
वैल्यू कहां दी गई थी. आईडीई में इस्तेमाल ढूंढें या Compose लेआउट इंस्पेक्टर जैसे टूल, इस समस्या को कम करने के लिए ज़रूरी जानकारी देते हैं.
तय करें कि 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
एपीआई का इस्तेमाल करते हैं:
// 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
से मिली वैल्यू दिखाता है. यह 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") } }
यह तरीका कुछ मामलों में बेहतर हो सकता है, क्योंकि इससे चाइल्ड को उसके पैरंट से अलग किया जा सकता है. ज़्यादा फ़्लेक्सिबल लोअर-लेवल कंपोज़ेबल बनाने के लिए, ऐनसेस्टर कंपोज़ेबल ज़्यादा जटिल हो जाते हैं.
इसी तरह, @Composable
कॉन्टेंट लैम्डा का इस्तेमाल भी उसी तरह किया जा सकता है, ताकि आपको ये फ़ायदे मिल सकें:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक टेक्स्ट दिखता है
- Compose में थीम के कॉम्पोनेंट
- 'लिखें' पेज में व्यू इस्तेमाल करना
- Jetpack Compose के लिए Kotlin