State ও Jetpack Compose

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

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

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

Jetpack Compose আপনাকে Android অ্যাপে কোথায় ও কীভাবে স্টেট স্টোর ও ব্যবহার করবেন সেই ব্যাপারে স্পষ্ট হতে সাহায্য করে। এই নির্দেশিকা স্টেট ও কম্পোজ করার উপযুক্ত আইটেমের মধ্যে কানেকশন এবং স্টেটের সাথে কাজ করার জন্য 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-এ কম্পোজিশন ও রিকম্পোজিশন কীভাবে কাজ করে তার জন্য এটি হয়।

প্রাথমিক কম্পোজিশন: প্রথমবার কম্পোজ করার উপযুক্ত আইটেম রান করানোর মাধ্যমে কম্পোজিশন তৈরি করা।

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

প্রাথমিক কম্পোজিশন ও রিকম্পোজিশন সম্পর্কে আরও জানতে, 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> তৈরি করার জন্য Compose-এ ফাংশন থাকে। এইসব ইন্টিগ্রেশন ব্যবহার করার আগে, নিম্নলিখিতভাবে উপযুক্ত আর্টিফ্যাক্ট যোগ করুন:

  • 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-এ ট্রান্সফর্ম করে।

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

    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-এর মধ্যে সীমাবদ্ধ নন। কম্পোজ করার উপযুক্ত আইটেমের জন্য আরও নির্দিষ্ট ইভেন্ট উপযুক্ত হলে, আপনাকে ল্যাম্বডা ব্যবহার করে সেগুলি নির্ধারণ করতে হবে।

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

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

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

@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 অ্যানোটেশন যোগ করা। অবজেক্টটি পার্সেল করার উপযুক্ত হয়ে যায় এবং বান্ডেল করা যায়। যেমন, এই কোডটি পার্সেলযোগ্য 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 স্টেট পৃষ্ঠা দেখুন।

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

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

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, 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 ফাংশন এক্সপোজ করে। এই ধরনের ফাংশন এক্সপোজ করে এমন একটি ইনস্ট্যান্স তৈরি করা যা রিকম্পোজিশনের পরেও টিকে থাকে, এটি 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 গণনা করা ল্যাম্বডা ব্লকটি আবার এক্সিকিউট করে।

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

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

আরও জানুন

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

নমুনা

কোডল্যাবস

ভিডিও

ব্লগ