CompositionLocal হলো Composition-এর মধ্য দিয়ে স্বয়ংক্রিয়ভাবে ডেটা পাঠানোর একটি টুল। এই পৃষ্ঠায়, আপনি আরও বিস্তারিতভাবে জানতে পারবেন CompositionLocal কী, কীভাবে নিজের CompositionLocal তৈরি করবেন, এবং আপনার ব্যবহারের ক্ষেত্রে CompositionLocal একটি ভালো সমাধান কিনা তা জানতে পারবেন।
CompositionLocal পরিচিতি স্থানীয়
সাধারণত কম্পোজে, ডেটা প্রতিটি কম্পোজেবল ফাংশনের প্যারামিটার হিসেবে UI ট্রি-এর মধ্য দিয়ে প্রবাহিত হয় । এর ফলে একটি কম্পোজেবলের নির্ভরতাগুলো সুস্পষ্ট হয়ে ওঠে। তবে, রঙ বা টাইপ স্টাইলের মতো খুব ঘন ঘন এবং ব্যাপকভাবে ব্যবহৃত ডেটার ক্ষেত্রে এটি ঝামেলার হতে পারে। নিচের উদাহরণটি দেখুন:
@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 প্রদান করে, যা আপনাকে ট্রি-স্কোপড নামযুক্ত অবজেক্ট তৈরি করার সুযোগ দেয় এবং UI ট্রি-এর মধ্য দিয়ে ডেটা প্রবাহের একটি পরোক্ষ উপায় হিসেবে ব্যবহার করা যায়।
CompositionLocal এলিমেন্টগুলোকে সাধারণত UI ট্রি-এর কোনো একটি নির্দিষ্ট নোডে একটি ভ্যালু প্রদান করা হয়। কম্পোজেবল ফাংশনে CompositionLocal প্যারামিটার হিসেবে ডিক্লেয়ার না করেই এর কম্পোজেবল ডিসেন্ডেন্টরা সেই ভ্যালুটি ব্যবহার করতে পারে।
Material থিম অভ্যন্তরীণভাবে CompositionLocal ব্যবহার করে। 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-এর একটি নির্দিষ্ট অংশের মধ্যে সীমাবদ্ধ থাকে, ফলে আপনি ট্রি-এর বিভিন্ন স্তরে ভিন্ন ভিন্ন মান প্রদান করতে পারেন। একটি CompositionLocal এর current মানটি Composition-এর সেই অংশে থাকা কোনো পূর্বপুরুষ দ্বারা প্রদত্ত নিকটতম মানের সাথে সঙ্গতিপূর্ণ হয়।
একটি CompositionLocal এ নতুন মান প্রদান করতে, CompositionLocalProvider এবং এর provides infix ফাংশনটি ব্যবহার করুন, যা একটি CompositionLocal key-কে একটি value সাথে যুক্ত করে। CompositionLocalProvider এর content lambda, CompositionLocal এর current প্রপার্টি অ্যাক্সেস করার সময় প্রদত্ত মানটি গ্রহণ করবে। যখন একটি নতুন মান প্রদান করা হয়, Compose তখন Composition-এর সেই অংশগুলিকে পুনর্গঠন করে যেগুলি CompositionLocal থেকে ডেটা পড়ে।
এর একটি উদাহরণ হিসেবে, LocalContentColor CompositionLocal টেক্সট এবং আইকনোগ্রাফির জন্য ব্যবহৃত পছন্দের কন্টেন্ট কালারটি থাকে, যা বর্তমান ব্যাকগ্রাউন্ড কালারের সাথে এর বৈসাদৃশ্য নিশ্চিত করে। নিম্নলিখিত উদাহরণে, Composition-এর বিভিন্ন অংশের জন্য ভিন্ন ভিন্ন মান সরবরাহ করতে 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 কম্পোজেবলটির প্রিভিউ। শেষ উদাহরণটিতে, Material composable-এর অভ্যন্তরে CompositionLocal ইনস্ট্যান্সগুলো ব্যবহৃত হয়েছিল। একটি 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 হলো Composition-এর মধ্য দিয়ে পরোক্ষভাবে ডেটা প্রেরণ করার একটি টুল।
CompositionLocal ব্যবহারের আরেকটি গুরুত্বপূর্ণ সংকেত হলো যখন প্যারামিটারটি ক্রস-কাটিং হয় এবং ইমপ্লিমেন্টেশনের মধ্যবর্তী স্তরগুলোর এর অস্তিত্ব সম্পর্কে অবগত থাকা উচিত নয়, কারণ ঐ মধ্যবর্তী স্তরগুলোকে অবগত করলে কম্পোজেবলটির উপযোগিতা সীমিত হয়ে যাবে। উদাহরণস্বরূপ, অ্যান্ড্রয়েড পারমিশন কোয়েরি করার কাজটি নেপথ্যে একটি CompositionLocal এর মাধ্যমেই করা হয়। একটি মিডিয়া পিকার কম্পোজেবল তার API পরিবর্তন না করেই ডিভাইসের পারমিশন-সুরক্ষিত কন্টেন্ট অ্যাক্সেস করার জন্য নতুন কার্যকারিতা যোগ করতে পারে এবং এর জন্য মিডিয়া পিকারের কলারদের এনভায়রনমেন্ট থেকে ব্যবহৃত এই অতিরিক্ত কনটেক্সট সম্পর্কে অবগত থাকারও প্রয়োজন হয় না।
তবে, CompositionLocal সবসময় সেরা সমাধান নয়। আমরা CompositionLocal অতিরিক্ত ব্যবহার নিরুৎসাহিত করি, কারণ এর কিছু অসুবিধা রয়েছে:
CompositionLocal একটি composable-এর আচরণ বোঝা আরও কঠিন করে তোলে । যেহেতু এগুলি অন্তর্নিহিত নির্ভরতা তৈরি করে, তাই যে composable-গুলি এগুলি ব্যবহার করে, তাদের কলারদের নিশ্চিত করতে হয় যে প্রতিটি CompositionLocal জন্য একটি মান সন্তুষ্ট হয়েছে।
তাছাড়া, এই ডিপেন্ডেন্সির কোনো সুস্পষ্ট উৎস নাও থাকতে পারে, কারণ এটি কম্পোজিশনের যেকোনো অংশে পরিবর্তিত হতে পারে। ফলে, কোনো সমস্যা দেখা দিলে অ্যাপটি ডিবাগ করা আরও কঠিন হয়ে পড়ে, কারণ current ভ্যালুটি কোথায় দেওয়া হয়েছিল তা দেখতে আপনাকে কম্পোজিশনের উপরের দিকে যেতে হয়। IDE-তে Find usages বা Compose লেআউট ইন্সপেক্টরের মতো টুলগুলো এই সমস্যাটি সমাধান করার জন্য যথেষ্ট তথ্য সরবরাহ করে।
CompositionLocal ব্যবহার করবেন কিনা তা স্থির করুন।
কিছু নির্দিষ্ট শর্ত রয়েছে যা আপনার ব্যবহারের ক্ষেত্রে CompositionLocal একটি ভালো সমাধান করে তুলতে পারে:
একটি CompositionLocal একটি উপযুক্ত ডিফল্ট মান থাকা উচিত । যদি কোনো ডিফল্ট মান না থাকে, তবে আপনাকে অবশ্যই নিশ্চিত করতে হবে যে একজন ডেভেলপারের পক্ষে এমন পরিস্থিতিতে পড়া অত্যন্ত কঠিন, যেখানে CompositionLocal টির জন্য কোনো মান প্রদান করা হয়নি। ডিফল্ট মান প্রদান না করা টেস্ট তৈরি করার সময় বা সেই CompositionLocal ব্যবহার করে এমন কোনো কম্পোজেবল প্রিভিউ করার সময় সমস্যা এবং হতাশার কারণ হতে পারে; এক্ষেত্রে সর্বদা মানটি স্পষ্টভাবে প্রদান করা আবশ্যক।
যেসব কনসেপ্টকে ট্রি-স্কোপড বা সাব-হায়ারার্কি স্কোপড হিসেবে বিবেচনা করা হয় না, সেগুলোর জন্য CompositionLocal করা এড়িয়ে চলুন । একটি CompositionLocal তখনই অর্থবহ হয়, যখন এটি যেকোনো ডিসেন্ডেন্ট দ্বারা সম্ভাব্যভাবে ব্যবহৃত হতে পারে, তাদের মধ্যে কয়েকজনের দ্বারা নয়।
যদি আপনার ব্যবহারের ক্ষেত্রটি এই প্রয়োজনীয়তাগুলি পূরণ না করে, তাহলে CompositionLocal তৈরি করার আগে 'বিবেচনা করার মতো বিকল্পসমূহ' বিভাগটি দেখে নিন।
একটি খারাপ অভ্যাসের উদাহরণ হলো একটি CompositionLocal তৈরি করা যা একটি নির্দিষ্ট স্ক্রিনের ViewModel ধারণ করে, যাতে সেই স্ক্রিনের সমস্ত কম্পোজেবল কিছু লজিক সম্পাদনের জন্য ViewModel এর একটি রেফারেন্স পেতে পারে। এটি একটি খারাপ অভ্যাস, কারণ একটি নির্দিষ্ট UI ট্রি-এর নিচের সমস্ত কম্পোজেবলের একটি ViewModel সম্পর্কে জানার প্রয়োজন নেই। ভালো অভ্যাস হলো , স্টেট নিচের দিকে এবং ইভেন্ট উপরের দিকে প্রবাহিত হওয়ার প্যাটার্ন অনুসরণ করে কম্পোজেবলগুলোতে শুধুমাত্র প্রয়োজনীয় তথ্য পাস করা। এই পদ্ধতি আপনার কম্পোজেবলগুলোকে আরও পুনঃব্যবহারযোগ্য এবং পরীক্ষা করা সহজ করে তুলবে।
একটি CompositionLocal তৈরি করুন
CompositionLocal তৈরি করার জন্য দুটি API আছে:
compositionLocalOf: পুনর্গঠনের সময় প্রদত্ত মান পরিবর্তন করলে শুধুমাত্র সেই কন্টেন্টটিই অবৈধ হয়ে যায় যা এরcurrentমানটি পাঠ করে।staticCompositionLocalOf:compositionLocalOfবিপরীতে,staticCompositionLocalOfএর রিড Compose দ্বারা ট্র্যাক করা হয় না। এর মান পরিবর্তন করলে, Composition-এর মধ্যে শুধুcurrentমানটি রিড করার জায়গাগুলো নয়, বরং যেখানেCompositionLocalপ্রদান করা হয়েছে সেই সম্পূর্ণcontentল্যাম্বডাটি পুনর্গঠিত হয়।
CompositionLocal এ প্রদত্ত মান যদি পরিবর্তন হওয়ার সম্ভাবনা খুব কম থাকে বা কখনোই পরিবর্তন না হয়, তাহলে পারফরম্যান্সের সুবিধা পেতে staticCompositionLocalOf ব্যবহার করুন।
উদাহরণস্বরূপ, একটি অ্যাপের ডিজাইন সিস্টেম UI কম্পোনেন্টের জন্য শ্যাডো ব্যবহার করে কম্পোজেবলগুলোকে কীভাবে উন্নত করা হয়, সে বিষয়ে নিজস্ব মতামত রাখতে পারে। যেহেতু অ্যাপের বিভিন্ন এলিভেশন পুরো 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 এ মান সরবরাহ করুন
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") } }
এই পদ্ধতিটি কিছু ব্যবহারের ক্ষেত্রে আরও উপযুক্ত হতে পারে, কারণ এটি চাইল্ডকে তার নিকটতম পূর্বপুরুষদের থেকে বিচ্ছিন্ন করে । আরও নমনীয় নিম্ন-স্তরের কম্পোজেবল থাকার সুবিধার জন্য পূর্বপুরুষ কম্পোজেবলগুলো আরও জটিল হয়ে ওঠার প্রবণতা দেখা যায়।
একইভাবে, একই সুবিধা পেতে @Composable কন্টেন্ট ল্যাম্বডাগুলোও একই উপায়ে ব্যবহার করা যেতে পারে:
@Composable fun MyComposable(myViewModel: MyViewModel = viewModel()) { // ... ReusablePartOfTheScreen( content = { Button( onClick = { myViewModel.loadData() } ) { Text("Confirm") } } ) } @Composable fun ReusablePartOfTheScreen(content: @Composable () -> Unit) { Column { // ... content() } }
আপনার জন্য প্রস্তাবিত
- দ্রষ্টব্য: জাভাস্ক্রিপ্ট বন্ধ থাকলেও লিঙ্কের লেখা প্রদর্শিত হয়।
- কম্পোজে একটি থিমের গঠন
- কম্পোজে ভিউ ব্যবহার করা
- জেটপ্যাক কম্পোজের জন্য কোটলিন