State ও Jetpack Compose

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

সব Android অ্যাপ ব্যবহারকারীকে স্ট্যাটাস দেখায়। Android অ্যাপে স্টেটের কিছু উদাহরণ:

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

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

স্টেট ও কম্পোজিশন

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

@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 প্যারামিটার পরিবর্তন হলে এটি আপডেট হয়। Compose-এ কম্পোজিশন ও রিকম্পোজিশন কীভাবে কাজ করে তার উপর এটি নির্ভর করে।

প্রাথমিক কম্পোজিশন: প্রথমবার কম্পোজেবল রান করার মাধ্যমে কম্পোজিশন তৈরি করা।

রিকম্পোজিশন: ডেটা পরিবর্তন হলে কম্পোজিশন আপডেট করার জন্য কম্পোজেবল আবার রান করা।

প্রাথমিক কম্পোজিশন ও রিকম্পোজিশন সম্পর্কে আরও জানতে, Thinking in Compose দেখুন।

কম্পোজেবলে স্টেট

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

mutableStateOf একটি পর্যবেক্ষণযোগ্য MutableState<T> তৈরি করে, যা কম্পোজ রানটাইমের সাথে ইন্টিগ্রেট করা একটি পর্যবেক্ষণযোগ্য ধরন।

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 ডেলিগেট সিনট্যাক্সের জন্য নিম্নলিখিত ইমপোর্ট প্রয়োজন:

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>-এ কনভার্ট করতে হবে যাতে স্টেট পরিবর্তন হলে কম্পোজেবলগুলি অটোমেটিক আবার কম্পোজ করতে পারে।

Android অ্যাপে ব্যবহৃত সাধারণ পর্যবেক্ষণযোগ্য প্রকার থেকে State<T> তৈরি করার জন্য কম্পোজ শিপে ফাংশন থাকে। এইসব ইন্টিগ্রেশন ব্যবহার করার আগে, নিম্নলিখিতভাবে উপযুক্ত আর্টিফ্যাক্ট যোগ করুন:

  • Flow: collectAsStateWithLifecycle()

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

    build.gradle ফাইলে নিম্নলিখিত ডিপেন্ডেন্সি প্রয়োজন (এটি 2.6.0-beta01 বা তার পরবর্তী যেকোনও ভার্সন হতে হবে):

Kotlin

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

গ্ৰুভি

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

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

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

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

  • LiveData: observeAsState()

    observeAsState() এই LiveData পর্যবেক্ষণ করা শুরু করে এবং State ব্যবহার করে এর ভ্যালু দেখায়।

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

Kotlin

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

গ্ৰুভি

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

Kotlin

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

গ্ৰুভি

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

Kotlin

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

গ্ৰুভি

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

স্টেটফুল বনাম স্টেটলেস

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

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

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

রাষ্ট্র উত্তোলন

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

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

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

এইভাবে হোস্ট করা স্টেটের কিছু গুরুত্বপূর্ণ প্রপার্টি আছে:

  • Single source of truth: স্টেট ডুপ্লিকেট করার পরিবর্তে তা সরিয়ে নিয়ে যাওয়ার মাধ্যমে, আমরা নিশ্চিত করছি যে সেখানে শুধুমাত্র একটি single source of truth আছে। এটি বাগ এড়াতে সাহায্য করে।
  • এনক্যাপসুলেট করা: শুধুমাত্র স্টেটফুল কম্পোজেবল তাদের স্টেট পরিবর্তন করতে পারে। এটি সম্পূর্ণ ইন্টার্নাল।
  • শেয়ার করা যায়: একাধিক কম্পোজেবলের সাথে হোইস্ট করা স্টেট শেয়ার করা যায়। আপনি যদি অন্য কোনও কম্পোজেবলে 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-তে স্টেট প্রদর্শন করে আপনার অ্যাপের সেই অংশগুলি থেকে যা স্টেট স্টোর এবং পরিবর্তন করে।

  1. স্টেটকে কমপক্ষে সেইসব কম্পোজেবলের সর্বনিম্ন সাধারণ পেরেন্ট-এ হোস্ট করতে হবে যেগুলি স্টেট ব্যবহার করে (রিড)।
  2. স্টেটকে কমপক্ষে সর্বোচ্চ লেভেলে হোইস্ট করতে হবে যেখানে এটি পরিবর্তন করা যেতে পারে (লিখুন)।
  3. একই ইভেন্টের উত্তরে দুটি স্টেট পরিবর্তন হলে সেগুলি একসাথে হোইস্ট করা উচিত।

আপনি এই নিয়মগুলির প্রয়োজনের চেয়ে বেশি স্টেট উত্তোলন করতে পারেন, কিন্তু স্টেট কম উত্তোলন করলে ইউনিডিরেকশনাল ডেটা ফ্লো অনুসরণ করা কঠিন বা অসম্ভব হয়ে পড়ে।

আরও জানতে স্টেট কোথায় হোস্ট করতে হবে পৃষ্ঠা দেখুন।

Compose-এ স্টেট রিস্টোর করা

rememberSaveable API, remember-এর মতোই কাজ করে কারণ এটি রিকম্পোজিশন জুড়ে এবং সেভ করা ইনস্ট্যান্স স্টেট মেকানিজম ব্যবহার করে অ্যাক্টিভিটি বা প্রসেস রিক্রিয়েশন জুড়ে স্টেট ধরে রাখে। যেমন, স্ক্রিন রোটেট করা হলে এটি হয়।

স্ট্যাটাস স্টোর করার পদ্ধতি

Bundle-এ যোগ করা সব ধরনের ডেটা অটোমেটিক সেভ হয়ে যায়। আপনি যদি এমন কিছু সেভ করতে চান যা Bundle-এ যোগ করা যাবে না, তাহলে বেশ কয়েকটি বিকল্প আছে।

Parcelize

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

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

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

MapSaver

কোনও কারণে @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"))
    }
}

ListSaver

ম্যাপের জন্য কী সংজ্ঞায়িত করার প্রয়োজন এড়াতে, আপনি 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-এ স্টেট হোল্ডার

কম্পোজেবল ফাংশনেই সাধারণ স্টেট হোইস্টিং ম্যানেজ করা যায়। তবে, ট্র্যাক করার জন্য স্টেটের পরিমাণ বৃদ্ধি পেলে অথবা কম্পোজেবল ফাংশনে পারফর্ম করার জন্য লজিক তৈরি হলে, লজিক ও স্টেটের দায়িত্ব অন্যান্য ক্লাসে ডেলিগেট করা ভালো অভ্যাস: স্টেট হোল্ডার।

মনে রাখবেন যে অন্যান্য মেটেরিয়ালে, স্টেট হোল্ডারকে হয়েস্টেড স্টেট অবজেক্টও বলা হয়।

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

'কী' পরিবর্তন হলে, গণনা মনে রাখার বিষয়টি আবার ট্রিগার করুন

remember API প্রায়ই MutableState-এর সাথে ব্যবহার করা হয়:

var name by remember { mutableStateOf("") }

এখানে, remember ফাংশন ব্যবহার করলে MutableState ভ্যালু রিকম্পোজিশন সারভাইভ করে।

সাধারণত, remember-এ calculation ল্যাম্বডা প্যারামিটার লাগে। remember প্রথমবার রান করা হলে, এটি calculation lambda-কে কল করে এবং এর ফলাফল স্টোর করে। রিকম্পোজিশনের সময়, remember শেষবার সেভ করা মান ফিরিয়ে দেয়।

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

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

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

এই মেকানিজম কীভাবে কাজ করে তা নিম্নলিখিত উদাহরণ থেকে দেখুন।

এই স্নিপেটে, একটি ShaderBrush তৈরি করা হয়েছে এবং একটি Box কম্পোজেবলের ব্যাকগ্রাউন্ড পেইন্ট হিসাবে ব্যবহার করা হয়েছে। remember ShaderBrush ইনস্ট্যান্স স্টোর করে কারণ এটি আবার তৈরি করা ব্যয়বহুল, যেমনটি আগে ব্যাখ্যা করা হয়েছে। remember avatarRes-কে key1 প্যারামিটার হিসেবে নেয়, যা হল বেছে নেওয়া ব্যাকগ্রাউন্ড ছবি। 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 ফাংশন প্রকাশ করে। এই ধরনের ফাংশন এক্সপোজ করে এমন ইনস্ট্যান্স তৈরি করা যা রিকম্পোজিশনের পরেও থেকে যায়, তা Compose-এ একটি সাধারণ প্যাটার্ন। 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 ক্যালকুলেশন ল্যাম্বডা ব্লক আবার এক্সিকিউট করে।

নিচের উদাহরণে, typedQuery পরিবর্তন না হওয়া পর্যন্ত rememberSaveable userTypedQuery স্টোর করে:

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

আরও জানুন

স্টেট ও Jetpack Compose সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত রিসোর্স দেখুন।

নমুনা

কোডল্যাবস

ভিডিও

ব্লগ