CompositionLocal-এর সাথে লোকালি স্কোপ করা ডেটা

CompositionLocal হল এমন একটি টুল যা কম্পোজিশনের মাধ্যমে ডেটা পাস করার জন্য ব্যবহার করা হয়। এই পৃষ্ঠায়, আপনি CompositionLocal কী সেই সম্পর্কে আরও বিস্তারিতভাবে জানতে পারবেন, কীভাবে আপনার নিজস্ব CompositionLocal তৈরি করবেন এবং আপনার ব্যবহারের ক্ষেত্রে CompositionLocal একটি ভালো সমাধান কিনা তা জানতে পারবেন।

CompositionLocal-এর ভূমিকা

সাধারণত Compose-এ, প্রতিটি কম্পোজেবল ফাংশনের প্যারামিটার হিসেবে 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
    )
}

বেশিরভাগ কম্পোজেবলকে স্পষ্ট প্যারামিটার ডিপেন্ডেন্সি হিসেবে রঙ পাস করার প্রয়োজনীয়তা এড়াতে, Compose CompositionLocal অফার করে, যা আপনাকে ট্রি-স্কোপড নামযুক্ত অবজেক্ট তৈরি করতে দেয় যা UI ট্রির মাধ্যমে ডেটা ফ্লো করার জন্য একটি অন্তর্নিহিত উপায় হিসেবে ব্যবহার করা যেতে পারে।

CompositionLocal এলিমেন্ট সাধারণত UI ট্রির নির্দিষ্ট নোডে একটি ভ্যালু সহ প্রদান করা হয়। কম্পোজেবল ফাংশনে CompositionLocal-কে প্যারামিটার হিসেবে ঘোষণা না করেই সেই ভ্যালু তার কম্পোজেবল ডিসেন্ডেন্ট ব্যবহার করতে পারে।

মূল শব্দ: এই গাইডে আমরা কম্পোজিশন, UI ট্রি এবং UI হায়ারার্কি শব্দগুলি ব্যবহার করি। যদিও অন্যান্য গাইডে এগুলি একে অপরের পরিবর্তে ব্যবহার করা হতে পারে, তবে এগুলির অর্থ আলাদা:

  • কম্পোজিশন হল কম্পোজেবল ফাংশনের কল গ্রাফের রেকর্ড।
  • UI ট্রি বা UI হায়ারার্কি হল LayoutNode-এর ট্রি যা কম্পোজিশন প্রসেসের মাধ্যমে তৈরি, আপডেট ও ম্যানেজ করা হয়।

CompositionLocal হল সেই জিনিস যা মেটিরিয়াল থিম আড়ালে ব্যবহার করে। 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 কম্পোজেবলের প্রিভিউ।
ছবি ১. CompositionLocalExample কম্পোজেবলের প্রিভিউ।

শেষ উদাহরণে, CompositionLocal ইনস্ট্যান্স ইন্টার্নালি ব্যবহার করা হয়েছে Material composables-এর মাধ্যমে। 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-এর মাধ্যমে করা হয়। মিডিয়া পিকার কম্পোজেবল ডিভাইসে অনুমতি-সুরক্ষিত কন্টেন্ট অ্যাক্সেস করার জন্য নতুন কার্যকারিতা যোগ করতে পারে। এর জন্য API পরিবর্তন করার প্রয়োজন হয় না এবং মিডিয়া পিকারের কলারদের পরিবেশ থেকে ব্যবহৃত এই অতিরিক্ত প্রসঙ্গ সম্পর্কে সচেতন হতে হয় না।

তবে, CompositionLocal সবসময় সেরা সমাধান নয়। আমরা CompositionLocal অতিরিক্ত ব্যবহার করতে নিরুৎসাহিত করি কারণ এর কিছু নেতিবাচক দিক আছে:

CompositionLocal কম্পোজেবলের আচরণ সম্পর্কে যুক্তি দেওয়া আরও কঠিন করে তোলে। যেহেতু এগুলি অন্তর্নিহিত ডিপেন্ডেন্সি তৈরি করে, তাই যে কম্পোজেবল এগুলি ব্যবহার করে, তার কলারকে নিশ্চিত করতে হবে যে প্রতিটি CompositionLocal-এর জন্য একটি ভ্যালু পূরণ করা হয়েছে।

এছাড়াও, এই ডিপেন্ডেন্সির জন্য কোনও স্পষ্ট সোর্স অফ ট্রুথ নাও থাকতে পারে কারণ এটি কম্পোজিশনের যেকোনও অংশে মিউটেট করতে পারে। তাই, কোনও সমস্যা হলে অ্যাপ ডিবাগ করা আরও কঠিন হতে পারে কারণ current ভ্যালু কোথায় দেওয়া হয়েছে তা দেখতে আপনাকে কম্পোজিশনের উপরের দিকে নেভিগেট করতে হবে। IDE-তে ব্যবহার খুঁজুন বা Compose লেআউট ইন্সপেক্টর-এর মতো টুল এই সমস্যা সমাধান করার জন্য যথেষ্ট তথ্য প্রদান করে।

CompositionLocal ব্যবহার করবেন কিনা তা ঠিক করুন

কিছু নির্দিষ্ট শর্ত আছে যা আপনার ব্যবহারের ক্ষেত্রে CompositionLocal-কে একটি ভালো সমাধান হিসেবে তুলে ধরতে পারে:

CompositionLocal-এর একটি ভালো ডিফল্ট ভ্যালু থাকা উচিত। ডিফল্ট ভ্যালু না থাকলে, আপনাকে নিশ্চিত করতে হবে যে ডেভেলপার যেন কোনওভাবেই এমন পরিস্থিতিতে না পড়েন যেখানে CompositionLocal-এর জন্য কোনও ভ্যালু প্রদান করা হয়নি। ডিফল্ট ভ্যালু না দিলে, টেস্ট তৈরি করার সময় বা কম্পোজেবল প্রিভিউ করার সময় সমস্যা ও হতাশা তৈরি হতে পারে, কারণ যে কম্পোজেবল CompositionLocal ব্যবহার করে, সেটির জন্য সবসময় স্পষ্টভাবে এটি প্রদান করতে হবে।

CompositionLocal এড়িয়ে চলুন এমন ধারণার জন্য যেগুলি ট্রি-স্কোপড বা সাব-হায়ারার্কি স্কোপড হিসেবে ভাবা হয় না। CompositionLocal তখনই অর্থপূর্ণ হয় যখন এটি সম্ভাব্যভাবে যেকোনও বংশধর ব্যবহার করতে পারে, তাদের মধ্যে কয়েকজন নয়।

আপনার ব্যবহারের ক্ষেত্রে যদি এইসব প্রয়োজনীয়তা পূরণ না হয়, তাহলে CompositionLocal তৈরি করার আগে বিবেচনা করার মতো বিকল্প বিভাগটি দেখুন।

খারাপ অনুশীলনের একটি উদাহরণ হল এমন একটি CompositionLocal তৈরি করা যা একটি নির্দিষ্ট স্ক্রিনের ViewModel ধরে রাখে যাতে সেই স্ক্রিনের সমস্ত কম্পোজেবল কিছু লজিক পারফর্ম করার জন্য ViewModel-এর একটি রেফারেন্স পেতে পারে। এটি একটি খারাপ অভ্যাস কারণ নির্দিষ্ট UI ট্রির নিচে থাকা সব কম্পোজেবলকে ViewModel সম্পর্কে জানতে হবে না। কম্পোজেবলকে শুধুমাত্র সেই তথ্য পাস করা ভালো যা তাদের প্রয়োজন, এই প্যাটার্ন অনুসরণ করে যে স্টেট নিচের দিকে প্রবাহিত হয় এবং ইভেন্ট উপরের দিকে প্রবাহিত হয়। এই পদ্ধতি আপনার কম্পোজেবলকে আরও বেশি করে ব্যবহারযোগ্য এবং সহজে পরীক্ষা করার উপযুক্ত করে তুলবে।

CompositionLocal তৈরি করুন

CompositionLocal তৈরি করার জন্য দুটি API আছে:

  • compositionLocalOf: রিকম্পোজিশনের সময় প্রদত্ত ভ্যালু পরিবর্তন করলে শুধুমাত্র সেই কন্টেন্ট অবৈধ হয়ে যায় যেটি এর current ভ্যালু রিড করে।

  • staticCompositionLocalOf: compositionLocalOf-এর মতো, staticCompositionLocalOf-এর রিড Compose ট্র্যাক করে না। ভ্যালু পরিবর্তন করলে, কম্পোজিশনে যেখানে 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()
    }
}