রাজ্য এবং জেটপ্যাক রচনা

একটি অ্যাপের স্টেট হলো এমন যেকোনো মান যা সময়ের সাথে সাথে পরিবর্তিত হতে পারে। এটি একটি অত্যন্ত ব্যাপক সংজ্ঞা এবং এর মধ্যে একটি রুম ডেটাবেস থেকে শুরু করে একটি ক্লাসের ভেরিয়েবল পর্যন্ত সবকিছুই অন্তর্ভুক্ত।

সমস্ত অ্যান্ড্রয়েড অ্যাপ ব্যবহারকারীকে স্টেট প্রদর্শন করে। অ্যান্ড্রয়েড অ্যাপে স্টেটের কয়েকটি উদাহরণ হলো:

  • একটি স্নাকবার যা নেটওয়ার্ক সংযোগ স্থাপন করা না গেলে প্রদর্শিত হয়।
  • একটি ব্লগ পোস্ট এবং এর সাথে সম্পর্কিত মন্তব্যসমূহ।
  • বাটনগুলোতে থাকা রিপল অ্যানিমেশন, যা ব্যবহারকারী ক্লিক করলে প্রদর্শিত হয়।
  • স্টিকার যা ব্যবহারকারী কোনো ছবির উপরে আঁকতে পারে।

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

অবস্থা এবং গঠন

Compose একটি ডিক্লারেটিভ প্রক্রিয়া এবং সেই কারণে এটিকে আপডেট করার একমাত্র উপায় হলো নতুন আর্গুমেন্ট সহ একই কম্পোজেবলকে কল করা। এই আর্গুমেন্টগুলো হলো UI স্টেটের উপস্থাপনা। যখনই কোনো স্টেট আপডেট করা হয়, একটি রিকম্পোজিশন ঘটে। এর ফলে, ইম্পারেটিভ XML ভিত্তিক ভিউয়ের মতো TextField মতো বিষয়গুলো স্বয়ংক্রিয়ভাবে আপডেট হয় না। একটি কম্পোজেবলকে সেই অনুযায়ী আপডেট করার জন্য নতুন স্টেট সম্পর্কে স্পষ্টভাবে জানাতে হয়।

@Composable
private fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello!",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(
            value = "",
            onValueChange = { },
            label = { Text("Name") }
        )
    }
}

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

প্রাথমিক গঠন ও পুনর্গঠন সম্পর্কে আরও জানতে, ‘থিংকিং ইন কম্পোজ’ দেখুন।

কম্পোজেবলগুলিতে অবস্থা

কম্পোজেবল ফাংশনগুলো মেমরিতে একটি অবজেক্ট সংরক্ষণ করতে remember এপিআই ব্যবহার করতে পারে। remember দ্বারা গণনা করা একটি মান প্রাথমিক কম্পোজিশনের সময় কম্পোজিশনে সংরক্ষিত হয় এবং রিকম্পোজিশনের সময় সংরক্ষিত মানটি ফেরত দেওয়া হয়। remember মিউটেবল এবং ইমিউটেবল উভয় প্রকার অবজেক্ট সংরক্ষণ করতে ব্যবহার করা যেতে পারে।

mutableStateOf একটি Observable MutableState<T> তৈরি করে, যা compose রানটাইমের সাথে সমন্বিত একটি Observable টাইপ।

interface MutableState<T> : State<T> {
    override var value: T
}

value যেকোনো পরিবর্তন, value পাঠকারী যেকোনো কম্পোজেবল ফাংশনের পুনর্গঠন নির্ধারণ করে।

একটি কম্পোজেবলে MutableState অবজেক্ট ঘোষণা করার তিনটি উপায় রয়েছে:

  • val mutableState = remember { mutableStateOf(default) }
  • var value by remember { mutableStateOf(default) }
  • val (value, setValue) = remember { mutableStateOf(default) }

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

by delegate সিনট্যাক্সটির জন্য নিম্নলিখিত ইম্পোর্টগুলো প্রয়োজন:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

আপনি মনে রাখা মানটিকে অন্যান্য কম্পোজেবলের জন্য প্যারামিটার হিসেবে, অথবা কোন কম্পোজেবলগুলো প্রদর্শিত হবে তা পরিবর্তন করার জন্য স্টেটমেন্টের লজিক হিসেবেও ব্যবহার করতে পারেন। উদাহরণস্বরূপ, নামটি খালি থাকলে আপনি যদি অভিবাদনটি প্রদর্শন করতে না চান, তাহলে একটি if স্টেটমেন্টে স্টেটটি ব্যবহার করুন:

@Composable
fun HelloContent() {
    Column(modifier = Modifier.padding(16.dp)) {
        var name by remember { mutableStateOf("") }
        if (name.isNotEmpty()) {
            Text(
                text = "Hello, $name!",
                modifier = Modifier.padding(bottom = 8.dp),
                style = MaterialTheme.typography.bodyMedium
            )
        }
        OutlinedTextField(
            value = name,
            onValueChange = { name = it },
            label = { Text("Name") }
        )
    }
}

যদিও remember আপনাকে পুনর্গঠনের সময় স্টেট ধরে রাখতে সাহায্য করে, কনফিগারেশন পরিবর্তনের সময় স্টেট সংরক্ষিত থাকে না। এর জন্য, আপনাকে অবশ্যই rememberSaveable ব্যবহার করতে হবে। rememberSaveable স্বয়ংক্রিয়ভাবে এমন যেকোনো ভ্যালু সংরক্ষণ করে যা একটি Bundle সংরক্ষণ করা যায়। অন্যান্য ভ্যালুর জন্য, আপনি একটি কাস্টম সেভার অবজেক্ট পাস করতে পারেন।

অন্যান্য সমর্থিত রাজ্যের প্রকারভেদ

Compose-এ স্টেট ধারণ করার জন্য MutableState<T> ব্যবহার করা বাধ্যতামূলক নয়; এটি অন্যান্য অবজার্ভেবল টাইপও সাপোর্ট করে। Compose-এ অন্য কোনো অবজার্ভেবল টাইপ পড়ার আগে, আপনাকে অবশ্যই সেটিকে State<T> তে রূপান্তর করতে হবে, যাতে স্টেট পরিবর্তিত হলে কম্পোজেবলগুলো স্বয়ংক্রিয়ভাবে পুনরায় কম্পোজ হতে পারে।

অ্যান্ড্রয়েড অ্যাপে ব্যবহৃত সাধারণ অবজার্ভেবল টাইপগুলো থেকে State<T> তৈরি করার জন্য Compose-এর সাথে ফাংশন অন্তর্ভুক্ত থাকে। এই ইন্টিগ্রেশনগুলো ব্যবহার করার আগে, নিচে বর্ণিত পদ্ধতি অনুযায়ী উপযুক্ত আর্টিফ্যাক্ট(গুলো) যোগ করুন:

  • Flow : collectAsStateWithLifecycle()

    collectAsStateWithLifecycle() একটি Flow (Flow) থেকে লাইফসাইকেল-সচেতন পদ্ধতিতে ভ্যালু সংগ্রহ করে, যা আপনার অ্যাপকে অ্যাপ রিসোর্স সংরক্ষণ করতে সাহায্য করে। এটি কম্পোজ State (Compose State) থেকে নির্গত সর্বশেষ ভ্যালুটিকে উপস্থাপন করে। অ্যান্ড্রয়েড অ্যাপে ফ্লো সংগ্রহ করার জন্য এই API-টি প্রস্তাবিত উপায় হিসেবে ব্যবহার করুন।

    build.gradle ফাইলে নিম্নলিখিত ডিপেন্ডেন্সিটি থাকা আবশ্যক (এটি 2.6.0-beta01 বা নতুন সংস্করণ হতে হবে):

কোটলিন

dependencies {
      ...
      implementation("androidx.lifecycle:lifecycle-runtime-compose:2.10.0")
}

গ্রুভি

dependencies {
      ...
      implementation "androidx.lifecycle:lifecycle-runtime-compose:2.10.0"
}
  • Flow : collectAsState()

    collectAsState collectAsStateWithLifecycle এর অনুরূপ, কারণ এটিও একটি Flow থেকে ভ্যালু সংগ্রহ করে এবং সেটিকে কম্পোজ State (Compose State)-এ রূপান্তরিত করে।

    প্ল্যাটফর্ম-নিরপেক্ষ কোডের জন্য collectAsStateWithLifecycle এর পরিবর্তে collectAsState ব্যবহার করুন, যা শুধুমাত্র অ্যান্ড্রয়েডের জন্য প্রযোজ্য।

    collectAsState জন্য কোনো অতিরিক্ত নির্ভরতার প্রয়োজন নেই, কারণ এটি compose-runtime এ উপলব্ধ।

  • LiveData : observeAsState()

    observeAsState() এই LiveData পর্যবেক্ষণ করা শুরু করে এবং এর মানগুলিকে State মাধ্যমে উপস্থাপন করে।

    build.gradle ফাইলে নিম্নলিখিত ডিপেন্ডেন্সিটি প্রয়োজন:

কোটলিন

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-livedata:1.10.5")
}

গ্রুভি

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-livedata:1.10.5"
}

কোটলিন

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava2:1.10.5")
}

গ্রুভি

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava2:1.10.5"
}

কোটলিন

dependencies {
      ...
      implementation("androidx.compose.runtime:runtime-rxjava3:1.10.5")
}

গ্রুভি

dependencies {
      ...
      implementation "androidx.compose.runtime:runtime-rxjava3:1.10.5"
}

রাষ্ট্রপূর্ণ বনাম রাষ্ট্রহীন

যে কম্পোজেবল অবজেক্ট সংরক্ষণের জন্য remember ব্যবহার করে, তা একটি অভ্যন্তরীণ স্টেট তৈরি করে, যা কম্পোজেবলটিকে স্টেটফুল করে তোলে। HelloContent একটি স্টেটফুল কম্পোজেবলের উদাহরণ, কারণ এটি অভ্যন্তরীণভাবে তার name স্টেট ধারণ ও পরিবর্তন করে। এটি এমন পরিস্থিতিতে কার্যকর হতে পারে যেখানে কলারের স্টেট নিয়ন্ত্রণ করার প্রয়োজন হয় না এবং তারা নিজেরা স্টেট পরিচালনা না করেই এটি ব্যবহার করতে পারে। তবে, অভ্যন্তরীণ স্টেটযুক্ত কম্পোজেবলগুলো সাধারণত কম পুনঃব্যবহারযোগ্য এবং পরীক্ষা করা কঠিন হয়।

স্টেটলেস কম্পোজেবল হলো এমন একটি কম্পোজেবল যা কোনো স্টেট ধারণ করে না। স্টেট হোস্টিং ব্যবহার করে স্টেটলেস অবস্থা অর্জন করা একটি সহজ উপায়।

পুনঃব্যবহারযোগ্য কম্পোজেবল তৈরি করার সময়, আপনি প্রায়শই একই কম্পোজেবলের একটি স্টেটফুল এবং একটি স্টেটলেস উভয় সংস্করণই প্রকাশ করতে চান। স্টেটফুল সংস্করণটি সেইসব কলারদের জন্য সুবিধাজনক যারা স্টেট নিয়ে মাথা ঘামায় না, এবং স্টেটলেস সংস্করণটি সেইসব কলারদের জন্য প্রয়োজনীয় যাদের স্টেট নিয়ন্ত্রণ বা হোইস্ট করার প্রয়োজন হয়।

রাষ্ট্রীয় উত্তোলন

কম্পোজে স্টেট হোস্টিং হলো একটি কম্পোজেবলকে স্টেটলেস করার জন্য তার স্টেটকে কলারে স্থানান্তর করার একটি পদ্ধতি। জেটপ্যাক কম্পোজে স্টেট হোস্টিং-এর সাধারণ প্যাটার্নটি হলো স্টেট ভেরিয়েবলকে দুটি প্যারামিটার দিয়ে প্রতিস্থাপন করা:

  • value: T : প্রদর্শনের জন্য বর্তমান মান
  • onValueChange: (T) -> Unit : একটি ইভেন্ট যা মান পরিবর্তনের অনুরোধ করে, যেখানে T হলো প্রস্তাবিত নতুন মান।

তবে, আপনি শুধু onValueChange মধ্যেই সীমাবদ্ধ নন। কম্পোজেবলটির জন্য যদি আরও সুনির্দিষ্ট ইভেন্টের প্রয়োজন হয়, তবে আপনার উচিত ল্যাম্বডা ব্যবহার করে সেগুলো সংজ্ঞায়িত করা।

এইভাবে উত্তোলিত অবস্থার কিছু গুরুত্বপূর্ণ বৈশিষ্ট্য রয়েছে:

  • তথ্যের একমাত্র উৎস: স্টেট ডুপ্লিকেট না করে তা স্থানান্তর করার মাধ্যমে আমরা নিশ্চিত করি যে তথ্যের উৎস একটাই। এটি বাগ এড়াতে সাহায্য করে।
  • এনক্যাপসুলেটেড: শুধুমাত্র স্টেটফুল কম্পোজেবলগুলোই তাদের স্টেট পরিবর্তন করতে পারে। এটি সম্পূর্ণ অভ্যন্তরীণ।
  • শেয়ারযোগ্য: হোইস্টেড স্টেট একাধিক কম্পোজেবলের সাথে শেয়ার করা যায়। আপনি যদি অন্য কোনো কম্পোজেবলে name পড়তে চান, হোইস্টিং আপনাকে তা করার সুযোগ দেবে।
  • ইন্টারসেপ্টেবল: স্টেটলেস কম্পোজেবলগুলোর কলাররা স্টেট পরিবর্তন করার আগে ইভেন্টগুলোকে উপেক্ষা বা পরিবর্তন করার সিদ্ধান্ত নিতে পারে।
  • বিচ্ছিন্ন: স্টেটলেস কম্পোজেবলগুলোর স্টেট যেকোনো জায়গায় সংরক্ষণ করা যেতে পারে। উদাহরণস্বরূপ, এখন name একটি ViewModel মধ্যে স্থানান্তর করা সম্ভব।

উদাহরণস্বরূপ, আপনি HelloContent থেকে name এবং onValueChange বের করে সেগুলোকে ট্রি-এর উপরের দিকে একটি HelloScreen কম্পোজেবলে নিয়ে যান, যা HelloContent কল করে।

@Composable
fun HelloScreen() {
    var name by rememberSaveable { mutableStateOf("") }

    HelloContent(name = name, onNameChange = { name = it })
}

@Composable
fun HelloContent(name: String, onNameChange: (String) -> Unit) {
    Column(modifier = Modifier.padding(16.dp)) {
        Text(
            text = "Hello, $name",
            modifier = Modifier.padding(bottom = 8.dp),
            style = MaterialTheme.typography.bodyMedium
        )
        OutlinedTextField(value = name, onValueChange = onNameChange, label = { Text("Name") })
    }
}

HelloContent থেকে স্টেটকে বাইরে নিয়ে আসার মাধ্যমে, কম্পোজেবল বিষয়টি বোঝা, বিভিন্ন পরিস্থিতিতে এর পুনঃব্যবহার এবং পরীক্ষা করা সহজ হয়। HelloContent তার স্টেট কীভাবে সংরক্ষিত হয়, তা থেকে বিচ্ছিন্ন থাকে। এই বিচ্ছিন্নতার অর্থ হলো, আপনি যদি HelloScreen পরিবর্তন বা প্রতিস্থাপন করেন, তাহলেও HelloContent বাস্তবায়নে কোনো পরিবর্তন আনতে হবে না।

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

আরও জানতে ‘Where to hoist state’ পৃষ্ঠাটি দেখুন।

কম্পোজে অবস্থা পুনরুদ্ধার করা

rememberSaveable API-টি remember মতোই আচরণ করে, কারণ এটি পুনর্বিন্যাসের পরেও স্টেট ধরে রাখে এবং সেভড ইনস্ট্যান্স স্টেট মেকানিজম ব্যবহার করে অ্যাক্টিভিটি বা প্রসেস পুনরায় তৈরি করার পরেও তা বজায় রাখে। উদাহরণস্বরূপ, স্ক্রিন ঘোরানোর সময় এটি ঘটে।

অবস্থা সংরক্ষণ করার উপায়

Bundle যোগ করা সমস্ত ডেটা টাইপ স্বয়ংক্রিয়ভাবে সংরক্ষিত হয়ে যায়। যদি আপনি এমন কিছু সংরক্ষণ করতে চান যা Bundle যোগ করা যায় না, তবে তার জন্য কয়েকটি বিকল্প রয়েছে।

পার্সেল করুন

সবচেয়ে সহজ সমাধান হলো অবজেক্টটিতে @Parcelize অ্যানোটেশনটি যোগ করা। এতে অবজেক্টটি পার্সেলযোগ্য হয়ে যায় এবং একে বান্ডল করা যায়। উদাহরণস্বরূপ, এই কোডটি একটি পার্সেলযোগ্য City ডেটা টাইপ তৈরি করে এবং এটিকে স্টেটে সংরক্ষণ করে।

@Parcelize
data class City(val name: String, val country: String) : Parcelable

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

ম্যাপসেভার

যদি কোনো কারণে @Parcelize উপযুক্ত না হয়, তাহলে আপনি mapSaver ব্যবহার করে একটি অবজেক্টকে একগুচ্ছ মানে রূপান্তর করার জন্য নিজস্ব নিয়ম নির্ধারণ করতে পারেন, যা সিস্টেম Bundle এ সংরক্ষণ করতে পারবে।

data class City(val name: String, val country: String)

val CitySaver = run {
    val nameKey = "Name"
    val countryKey = "Country"
    mapSaver(
        save = { mapOf(nameKey to it.name, countryKey to it.country) },
        restore = { City(it[nameKey] as String, it[countryKey] as String) }
    )
}

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

লিস্টসেভার

ম্যাপের জন্য কী (key) সংজ্ঞায়িত করার প্রয়োজনীয়তা এড়াতে, আপনি listSaver ব্যবহার করতে পারেন এবং এর ইনডেক্সগুলোকে কী হিসেবে ব্যবহার করতে পারেন:

data class City(val name: String, val country: String)

val CitySaver = listSaver<City, Any>(
    save = { listOf(it.name, it.country) },
    restore = { City(it[0] as String, it[1] as String) }
)

@Composable
fun CityScreen() {
    var selectedCity = rememberSaveable(stateSaver = CitySaver) {
        mutableStateOf(City("Madrid", "Spain"))
    }
}

কম্পোজে রাজ্য ধারক

সাধারণ স্টেট হোস্টিং কম্পোজেবল ফাংশনগুলোর মধ্যেই পরিচালনা করা যায়। তবে, যদি ট্র্যাক করার মতো স্টেটের পরিমাণ বেড়ে যায়, অথবা কম্পোজেবল ফাংশনগুলোতে সম্পাদন করার মতো লজিকের প্রয়োজন হয়, তাহলে সেই লজিক এবং স্টেটের দায়িত্বগুলো অন্যান্য ক্লাস—অর্থাৎ স্টেট হোল্ডারদের —কাছে অর্পণ করা একটি উত্তম অভ্যাস।

আরও জানতে Compose ডকুমেন্টেশনে স্টেট হোস্টিং (state hoisting) অথবা, আরও সাধারণভাবে, আর্কিটেকচার গাইডের স্টেট হোল্ডারস (State holders) এবং ইউআই স্টেট (UI State) পৃষ্ঠাটি দেখুন।

কী পরিবর্তন হলে গণনা মনে রাখার জন্য পুনরায় ট্রিগার করুন।

remember এপিআই প্রায়শই MutableState সাথে একত্রে ব্যবহৃত হয়:

var name by remember { mutableStateOf("") }

এখানে, remember ফাংশনটি ব্যবহার করলে MutableState মানটি পুনর্গঠনের পরেও অক্ষত থাকে।

সাধারণত, remember একটি calculation ল্যাম্বডা প্যারামিটার গ্রহণ করে। যখন remember প্রথমবার চালানো হয়, তখন এটি calculation ল্যাম্বডাকে আহ্বান করে এবং এর ফলাফল সংরক্ষণ করে। পুনর্গঠনের সময়, remember সর্বশেষ সংরক্ষিত মানটি ফেরত দেয়।

স্টেট ক্যাশিং ছাড়াও, কম্পোজিশনে এমন যেকোনো অবজেক্ট বা অপারেশনের ফলাফল সংরক্ষণ করতে আপনি remember ব্যবহার করতে পারেন, যা ইনিশিয়ালাইজ বা গণনা করতে ব্যয়বহুল। আপনি হয়তো প্রতিটি রিকম্পোজিশনে এই গণনাটির পুনরাবৃত্তি করতে চাইবেন না। একটি উদাহরণ হলো এই ShaderBrush অবজেক্টটি তৈরি করা, যা একটি ব্যয়বহুল অপারেশন:

val brush = remember {
    ShaderBrush(
        BitmapShader(
            ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
            Shader.TileMode.REPEAT,
            Shader.TileMode.REPEAT
        )
    )
}

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

নিম্নলিখিত উদাহরণগুলো দেখায় এই প্রক্রিয়াটি কীভাবে কাজ করে।

এই কোড স্নিপেটে, একটি ShaderBrush তৈরি করা হয়েছে এবং একটি Box কম্পোজেবলের ব্যাকগ্রাউন্ড পেইন্ট হিসেবে ব্যবহার করা হয়েছে। remember ShaderBrush ইনস্ট্যান্সটি সংরক্ষণ করা হয় কারণ এটি পুনরায় তৈরি করা ব্যয়বহুল, যেমনটি আগে ব্যাখ্যা করা হয়েছে। remember এটি key1 প্যারামিটার হিসেবে avatarRes গ্রহণ করে, যা হলো নির্বাচিত ব্যাকগ্রাউন্ড ইমেজ। যদি avatarRes পরিবর্তিত হয়, ব্রাশটি নতুন ইমেজ দিয়ে পুনরায় কম্পোজ করে এবং Box টিতে পুনরায় প্রয়োগ করা হয়। এটি তখন ঘটতে পারে যখন ব্যবহারকারী একটি পিকার থেকে ব্যাকগ্রাউন্ড হিসেবে অন্য কোনো ইমেজ নির্বাচন করেন।

@Composable
private fun BackgroundBanner(
    @DrawableRes avatarRes: Int,
    modifier: Modifier = Modifier,
    res: Resources = LocalContext.current.resources
) {
    val brush = remember(key1 = avatarRes) {
        ShaderBrush(
            BitmapShader(
                ImageBitmap.imageResource(res, avatarRes).asAndroidBitmap(),
                Shader.TileMode.REPEAT,
                Shader.TileMode.REPEAT
            )
        )
    }

    Box(
        modifier = modifier.background(brush)
    ) {
        /* ... */
    }
}

পরবর্তী কোড স্নিপেটে, স্টেটকে একটি সাধারণ স্টেট হোল্ডার ক্লাস MyAppState এ হোইস্ট করা হয়। এটি remember ব্যবহার করে ক্লাসটির একটি ইনস্ট্যান্স ইনিশিয়ালাইজ করার জন্য একটি rememberMyAppState ফাংশন প্রকাশ করে। রিকম্পোজিশনের পরেও টিকে থাকে এমন একটি ইনস্ট্যান্স তৈরি করার জন্য এই ধরনের ফাংশন প্রকাশ করা কম্পোজে একটি প্রচলিত রীতি। rememberMyAppState ফাংশনটি windowSizeClass গ্রহণ করে, যা remember জন্য key প্যারামিটার হিসেবে কাজ করে। যদি এই প্যারামিটারটি পরিবর্তিত হয়, তবে অ্যাপটিকে সর্বশেষ মান দিয়ে সাধারণ স্টেট হোল্ডার ক্লাসটি পুনরায় তৈরি করতে হবে। উদাহরণস্বরূপ, ব্যবহারকারী ডিভাইসটি ঘোরালে এটি ঘটতে পারে।

@Composable
private fun rememberMyAppState(
    windowSizeClass: WindowSizeClass
): MyAppState {
    return remember(windowSizeClass) {
        MyAppState(windowSizeClass)
    }
}

@Stable
class MyAppState(
    private val windowSizeClass: WindowSizeClass
) { /* ... */ }

কোনো কী পরিবর্তিত হয়েছে কিনা তা নির্ধারণ করতে এবং সংরক্ষিত মানটিকে বাতিল করতে Compose ক্লাসটির equals ইমপ্লিমেন্টেশন ব্যবহার করে।

পুনর্গঠন-অযোগ্য কী-সহ স্টেট সংরক্ষণ করুন।

rememberSaveable API হলো remember এর একটি র‍্যাপার যা একটি Bundle এ ডেটা সংরক্ষণ করতে পারে। এই API শুধুমাত্র পুনর্গঠনের ক্ষেত্রেই নয়, বরং অ্যাক্টিভিটির পুনঃসৃষ্টি এবং সিস্টেম-প্রবর্তিত প্রসেসের মৃত্যুর পরেও স্টেটকে অক্ষুণ্ণ রাখতে সাহায্য করে। rememberSaveable ঠিক সেই উদ্দেশ্যেই input প্যারামিটার গ্রহণ করে, যে উদ্দেশ্যে remember keys গ্রহণ করে। যেকোনো ইনপুট পরিবর্তিত হলে ক্যাশেটি অবৈধ হয়ে যায় । পরবর্তী বার যখন ফাংশনটি পুনর্গঠিত হয়, rememberSaveable ক্যালকুলেশন ল্যাম্বডা ব্লকটি পুনরায় কার্যকর করে।

নিম্নলিখিত উদাহরণে, rememberSaveable userTypedQuery ততক্ষণ পর্যন্ত সংরক্ষণ করে যতক্ষণ না typedQuery পরিবর্তিত হয়:

var userTypedQuery by rememberSaveable(typedQuery, stateSaver = TextFieldValue.Saver) {
    mutableStateOf(
        TextFieldValue(text = typedQuery, selection = TextRange(typedQuery.length))
    )
}

আরও জানুন

স্টেট এবং জেটপ্যাক কম্পোজ সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত রিসোর্সগুলো দেখুন।

নমুনা

কোডল্যাবস

ভিডিও

ব্লগ

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