CompositionLocal সহ স্থানীয়ভাবে স্কোপড ডেটা

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 কম্পোজেবলটির প্রিভিউ।
চিত্র ১. 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()
    }
}

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}