Compose-এ স্টেটের মেয়াদ

Jetpack Compose-এ, কম্পোজেবল ফাংশন প্রায়ই remember ফাংশন ব্যবহার করে স্টেট ধরে রাখে। মনে রাখা ভ্যালু, রিকম্পোজিশন জুড়ে আবার ব্যবহার করা যেতে পারে, যেমনটি State ও Jetpack Compose-এ ব্যাখ্যা করা হয়েছে।

remember রিকম্পোজিশন জুড়ে ভ্যালু বজায় রাখার টুল হিসেবে কাজ করলেও, স্টেটকে প্রায়শই কম্পোজিশনের লাইফটাইমের পরেও বজায় রাখতে হয়। এই পৃষ্ঠায় remember, retain, rememberSaveable এবং rememberSerializable API-এর মধ্যে পার্থক্য, কখন কোন API বেছে নিতে হবে এবং Compose-এ মনে রাখা ও ধরে রাখা ভ্যালু ম্যানেজ করার জন্য কোন কোন পেশাদার পদ্ধতি অনুসরণ করতে হবে তা ব্যাখ্যা করা হয়েছে।

সঠিক আয়ু বেছে নিন

Compose-এ, এমন অনেক ফাংশন আছে যা আপনি কম্পোজিশন এবং তার বাইরেও স্টেট পারসিস্ট করতে ব্যবহার করতে পারেন: remember, retain, rememberSaveable এবং rememberSerializable। এই ফাংশনগুলি তাদের লাইফস্প্যান এবং সিম্যান্টিক্সে আলাদা, এবং প্রতিটি নির্দিষ্ট ধরনের স্টেট স্টোর করার জন্য উপযুক্ত। পার্থক্যগুলি নিম্নলিখিত সারণীতে উল্লেখ করা হয়েছে:

remember

retain

rememberSaveable, rememberSerializable

Values survive recompositions?

✅

✅

✅

অ্যাক্টিভিটি রিক্রিয়েশন থেকে ভ্যালু কি বেঁচে যায়?

❌

✅

একই (===) ইনস্ট্যান্স সবসময় ফেরত পাঠানো হবে

✅

একটি সমতুল্য (==) অবজেক্ট ফেরত দেওয়া হবে, সম্ভবত একটি ডিসিরিয়ালাইজড কপি

প্রসেস বন্ধ করার পরেও কি ভ্যালু থেকে যায়?

❌

❌

✅

যেসব ডেটার ধরন কাজ করে

সব

অ্যাক্টিভিটি নষ্ট হয়ে গেলে ফাঁস হয়ে যেতে পারে এমন কোনও অবজেক্টের রেফারেন্স দেওয়া যাবে না

সিরিয়ালাইজ করা যেতে হবে
(কাস্টম Saver অথবা kotlinx.serialization-এর মাধ্যমে)

উদাহরণ

  • কম্পোজিশনের স্কোপে থাকা অবজেক্ট
  • কম্পোজেবলের জন্য কনফিগারেশন অবজেক্ট
  • UI ফিডেলিটি না হারিয়েই স্টেট আবার তৈরি করা যেতে পারে
  • ক্যাশে
  • দীর্ঘস্থায়ী বা "ম্যানেজার" অবজেক্ট
  • ব্যবহারকারীর ইনপুট
  • অ্যাপের মাধ্যমে আবার তৈরি করা যায় না এমন অবস্থা, যার মধ্যে টেক্সট ফিল্ড ইনপুট, স্ক্রল অবস্থা, টগল ইত্যাদি অন্তর্ভুক্ত।

remember

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

কম্পোজেবল ফাংশনের প্রতিটি ইনস্ট্যান্সের নিজস্ব মনে রাখা ভ্যালুর সেট থাকে, যাকে পজিশনাল মেমোইজেশন বলা হয়। মনে রাখা ভ্যালু যখন রিকম্পোজিশন জুড়ে ব্যবহারের জন্য মেমোয়াইজ করা হয়, তখন সেগুলি কম্পোজিশন হায়ারার্কিতে তাদের পজিশনের সাথে যুক্ত থাকে। কোনও কম্পোজেবল বিভিন্ন লোকেশনে ব্যবহার করা হলে, কম্পোজিশন হায়ারার্কির প্রতিটি ইনস্ট্যান্সের নিজস্ব মনে রাখা ভ্যালুর সেট থাকে।

মনে রাখা কোনও ভ্যালু আর ব্যবহার করা না হলে, সেটি ভুলে যাওয়া হয় এবং সেটির রেকর্ড বাতিল করা হয়। মনে রাখা ভ্যালু কম্পোজিশন হায়ারার্কি থেকে সরিয়ে দিলে (যার মধ্যে অন্তর্ভুক্ত হল, key কম্পোজেবল বা MovableContent ব্যবহার না করে অন্য লোকেশনে সরানোর জন্য কোনও ভ্যালু সরিয়ে আবার যোগ করা) বা অন্য key প্যারামিটার দিয়ে কল করা হলে, সেগুলি ভুলে যাওয়া হয়।

উপলভ্য বিকল্পগুলির মধ্যে, remember-এর লাইফস্প্যান সবচেয়ে কম এবং এই পৃষ্ঠায় বর্ণিত চারটি মেমোইজেশন ফাংশনের মধ্যে সবচেয়ে আগে ভ্যালু ভুলে যায়। এটি নিম্নলিখিত ক্ষেত্রে সবচেয়ে উপযুক্ত:

  • ইন্টার্নাল স্টেট অবজেক্ট তৈরি করা, যেমন স্ক্রল পজিশন বা অ্যানিমেশন স্টেট
  • প্রতিটি রিকম্পোজিশনে ব্যয়বহুল অবজেক্ট রিক্রিয়েশন এড়ানো

তবে, আপনাকে এগুলি এড়িয়ে চলতে হবে:

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

rememberSaveable ও rememberSerializable

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

rememberSerializable, rememberSaveable-এর মতোই কাজ করে, তবে kotlinx.serialization লাইব্রেরির সাথে সিরিয়ালাইজ করা যায় এমন জটিল ধরনের ডেটা অটোমেটিক সেভ করে রাখে। আপনার টাইপ @Serializable দিয়ে চিহ্নিত করা হলে (বা করা যেতে পারলে) rememberSerializable বেছে নিন এবং অন্য সব ক্ষেত্রে rememberSaveable বেছে নিন।

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

মনে রাখবেন যে rememberSaveable এবং rememberSerializable তাদের মেমোয়াইজ করা ভ্যালু Bundle-এ সিরিয়ালাইজ করে সেভ করে। এর দুটি ফলাফল আছে:

  • আপনি যে ভ্যালু মেমোয়াইজ করবেন তা নিম্নলিখিত এক বা একাধিক ডেটার ধরন দ্বারা উপস্থাপনযোগ্য হতে হবে: প্রিমিটিভ (Int, Long, Float, Double সহ), String অথবা এই ধরনের যেকোনও অ্যারে।
  • সেভ করা ভ্যালু রিস্টোর করা হলে, এটি একটি নতুন ইনস্ট্যান্স হবে যা (==)-এর সমান, কিন্তু কম্পোজিশন আগে যে রেফারেন্স (===) ব্যবহার করেছিল তার সমান নয়।

kotlinx.serialization ব্যবহার না করে আরও জটিল ডেটার ধরন স্টোর করতে, আপনি সাপোর্ট করে এমন ডেটার ধরন অনুযায়ী আপনার অবজেক্টকে সিরিয়ালাইজ ও ডিসিরিয়ালাইজ করতে কাস্টম Saver প্রয়োগ করতে পারেন। মনে রাখবেন যে Compose সাধারণ ডেটার ধরন যেমন State, List, Map, Set ইত্যাদি আউট-অফ-দ্য-বক্স হিসেবে বোঝে এবং আপনার হয়ে এগুলি অটোমেটিক কাজ করে এমন ধরনের ডেটায় কনভার্ট করে। Size ক্লাসের জন্য Saver-এর একটি উদাহরণ নিচে দেওয়া হল। listSaver ব্যবহার করে Size-এর সব প্রপার্টি একটি তালিকায় প্যাক করার মাধ্যমে এটি প্রয়োগ করা হয়।

data class Size(val x: Int, val y: Int) {
    object Saver : androidx.compose.runtime.saveable.Saver<Size, Any> by listSaver(
        save = { listOf(it.x, it.y) },
        restore = { Size(it[0], it[1]) }
    )
}

@Composable
fun rememberSize(x: Int, y: Int) {
    rememberSaveable(x, y, saver = Size.Saver) {
        Size(x, y)
    }
}

retain

retain API, remember এবং rememberSaveable/rememberSerializable-এর মধ্যে থাকে, এটি কতক্ষণ এর মান মেমোয়াইজ করে তার উপর ভিত্তি করে। এটির নাম আলাদা কারণ রিটেন করা ভ্যালুও তাদের মনে রাখা কাউন্টারপার্টের থেকে আলাদা লাইফসাইকেল অনুভব করে।

কোনও ভ্যালু রিটেন করা হলে, সেটি পজিশন অনুযায়ী মেমোয়াইজ করা হয় এবং সেকেন্ডারি ডেটা স্ট্রাকচারে সেভ করা হয়। এই সেকেন্ডারি ডেটা স্ট্রাকচারের লাইফস্প্যান আলাদা হয় এবং সেটি অ্যাপের লাইফস্প্যানের সাথে যুক্ত থাকে। একটি রিটেন করা ভ্যালু সিরিয়ালাইজ না করেই কনফিগারেশন পরিবর্তন থেকে বেঁচে থাকতে পারে, কিন্তু প্রসেস বন্ধ করা থেকে বেঁচে থাকতে পারে না। কম্পোজিশন হায়ারার্কি আবার তৈরি করার পরে কোনও ভ্যালু ব্যবহার না করা হলে, ধরে রাখা ভ্যালু রিটায়ার (যা retain-এর ভুলে যাওয়ার সমতুল্য) করে দেওয়া হয়।

rememberSaveable-এর চেয়ে ছোট এই লাইফসাইকেলের বিনিময়ে, retain এমন ভ্যালু সেভ করে রাখতে পারে যা সিরিয়ালাইজ করা যায় না, যেমন ল্যাম্বডা এক্সপ্রেশন, ফ্লো এবং বিটম্যাপের মতো বড় অবজেক্ট। যেমন, কনফিগারেশন পরিবর্তনের সময় মিডিয়া প্লেব্যাক যাতে বাধাপ্রাপ্ত না হয়, তার জন্য আপনি retain ব্যবহার করে মিডিয়া প্লেয়ার (যেমন ExoPlayer) ম্যানেজ করতে পারেন।

@Composable
fun MediaPlayer() {
    // Use the application context to avoid a memory leak
    val applicationContext = LocalContext.current.applicationContext
    val exoPlayer = retain { ExoPlayer.Builder(applicationContext).apply { /* ... */ }.build() }
    // ...
}

retain বনাম ViewModel

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

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

এছাড়াও, ViewModel-এ Dagger ও Hilt-এর সাথে ডিপেন্ডেন্সি ইনজেকশনের জন্য আউট-অফ-দ্য-বক্স ইন্টিগ্রেশন, SavedState-এর সাথে ইন্টিগ্রেশন এবং ব্যাকগ্রাউন্ড টাস্ক লঞ্চ করার জন্য বিল্ট-ইন কোরুটিন সাপোর্ট অন্তর্ভুক্ত। এটি ViewModel-কে ব্যাকগ্রাউন্ড টাস্ক ও নেটওয়ার্ক অনুরোধ লঞ্চ করা, আপনার প্রোজেক্টে অন্যান্য ডেটা সোর্সের সাথে ইন্টার‍্যাক্ট করা এবং ঐচ্ছিকভাবে মিশন-ক্রিটিক্যাল UI স্টেট ক্যাপচার ও বজায় রাখার জন্য একটি আদর্শ জায়গা করে তোলে। এই স্টেট ViewModel-এ কনফিগারেশন পরিবর্তন জুড়ে বজায় রাখা এবং প্রসেস বন্ধ করার পরেও টিকে থাকা উচিত।

retain এমন অবজেক্টের জন্য সবচেয়ে উপযুক্ত যেগুলি নির্দিষ্ট কম্পোজেবল ইনস্ট্যান্সের মধ্যে সীমাবদ্ধ এবং একই লেভেলের কম্পোজেবলের মধ্যে শেয়ার বা আবার ব্যবহার করার প্রয়োজন হয় না। যেখানে ViewModel UI স্টেট স্টোর করা এবং ব্যাকগ্রাউন্ড টাস্ক পারফর্ম করার জন্য একটি ভালো জায়গা হিসেবে কাজ করে, retain ক্যাশের মতো UI প্লাম্বিংয়ের জন্য অবজেক্ট স্টোর করার জন্য একটি ভালো ক্যান্ডিডেট, ইম্প্রেশন ট্র্যাকিং এবং অ্যানালিটিক্স, AndroidView-এর উপর নির্ভরতা এবং অন্যান্য অবজেক্ট যা Android OS-এর সাথে ইন্টার‍্যাক্ট করে বা পেমেন্ট প্রসেসর বা বিজ্ঞাপনের মতো থার্ড-পার্টি লাইব্রেরি ম্যানেজ করে।

Modern Android অ্যাপ আর্কিটেকচার সংক্রান্ত সাজেশনের বাইরে কাস্টম অ্যাপ আর্কিটেকচার প্যাটার্ন ডিজাইন করা উন্নত ব্যবহারকারীদের জন্য: retain-এর মাধ্যমে ইন-হাউস "ViewModel-এর মতো" API তৈরি করা যেতে পারে। যদিও coroutines এবং সেভ-স্টেটের জন্য সহায়তা আউট-অফ-দ্য-বক্স হিসেবে অফার করা হয় না, retain এই ধরনের ViewModel-এর মতো দেখতে লাইফসাইকেলের জন্য বিল্ডিং ব্লক হিসেবে কাজ করতে পারে, যেখানে এইসব ফিচার উপরে তৈরি করা হয়। এই ধরনের কম্পোনেন্ট কীভাবে ডিজাইন করতে হয় তার নির্দিষ্ট বিবরণ এই গাইডের আওতার বাইরে।

retain

ViewModel

Scoping

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

ViewModel হল ViewModelStore-এর মধ্যে সিঙ্গলটন

ধ্বংস করা

কম্পোজিশন হায়ারার্কি থেকে স্থায়ীভাবে বেরিয়ে আসার সময়

কখন ViewModelStore মুছে ফেলা বা নষ্ট করা হয়েছে

অতিরিক্ত কার্যকারিতা

অবজেক্ট কম্পোজিশন হায়ারার্কিতে থাকলে বা না থাকলে কলব্যাক পেতে পারে

বিল্ট-ইন coroutineScope, SavedStateHandle-এর জন্য সহায়তা, Hilt ব্যবহার করে ইনজেক্ট করা যেতে পারে

মালিক

RetainedValuesStore

ViewModelStore

ব্যবহারিক ক্ষেত্র

  • প্রতিটি কম্পোজেবল ইনস্ট্যান্সের জন্য UI-নির্দিষ্ট ভ্যালু লোকাল স্টোরেজে সেভ করা
  • ইম্প্রেশন ট্র্যাকিং, সম্ভবত RetainedEffect-এর মাধ্যমে
  • কাস্টম "ViewModel-এর মতো" আর্কিটেকচার কম্পোনেন্ট সংজ্ঞায়িত করার জন্য বিল্ডিং ব্লক
  • কোড অর্গানাইজেশন ও টেস্টিং, দুটির জন্যই UI ও ডেটা লেয়ারের মধ্যে ইন্টার‍্যাকশন আলাদা ক্লাসে এক্সট্র্যাক্ট করা
  • Flow-কে State অবজেক্টে ট্রান্সফর্ম করা এবং সাসপেন্ড ফাংশন কল করা যা কনফিগারেশন পরিবর্তনের কারণে বাধাগ্রস্ত হওয়া উচিত নয়
  • সম্পূর্ণ স্ক্রিনের মতো বড় UI এরিয়া জুড়ে শেয়ারিং স্ট্যাটাস দেখানো
  • View-এর সাথে ইন্টারঅপারেবিলিটি

retain এবং rememberSaveable বা rememberSerializable একত্রিত করুন

কখনও কখনও, কোনও অবজেক্টের হাইব্রিড লাইফস্প্যান retained এবং rememberSaveable অথবা rememberSerializable হতে হয়। এটি ইঙ্গিত করতে পারে যে আপনার অবজেক্টটি ViewModel হওয়া উচিত, যা ViewModel গাইডের জন্য সেভ করা স্টেট মডিউলে বর্ণনা করা সেভ করা স্টেটকে সাপোর্ট করতে পারে।

একই সাথে retain এবং rememberSaveable অথবা rememberSerializable ব্যবহার করা সম্ভব। দুটি লাইফসাইকেল সঠিকভাবে একত্রিত করলে তা উল্লেখযোগ্যভাবে জটিল হয়ে ওঠে। আমরা আরও উন্নত এবং কাস্টম আর্কিটেকচার প্যাটার্নের অংশ হিসেবে এই প্যাটার্নটি ব্যবহার করার সাজেশন দিই এবং শুধুমাত্র তখনই যখন নিম্নলিখিত সবকটি শর্ত পূরণ হয়:

  • আপনি এমন একটি অবজেক্ট সংজ্ঞায়িত করছেন যা বিভিন্ন ধরনের ভ্যালু নিয়ে গঠিত এবং এগুলি অবশ্যই রেখে দিতে বা সেভ করতে হবে (যেমন, এমন একটি অবজেক্ট যা ব্যবহারকারীর ইনপুট ট্র্যাক করে এবং ইন-মেমরি ক্যাশে যা ডিস্কে লেখা যায় না)
  • আপনার স্টেট কম্পোজেবলের জন্য স্কোপ করা হয়েছে এবং সিঙ্গলটন স্কোপিং বা ViewModel-এর লাইফস্প্যানের জন্য উপযুক্ত নয়

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

@Composable
fun rememberAndRetain(): CombinedRememberRetained {
    val saveData = rememberSerializable(serializer = serializer<ExtractedSaveData>()) {
        ExtractedSaveData()
    }
    val retainData = retain { ExtractedRetainData() }
    return remember(saveData, retainData) {
        CombinedRememberRetained(saveData, retainData)
    }
}

@Serializable
data class ExtractedSaveData(
    // All values that should persist process death should be managed by this class.
    var savedData: AnotherSerializableType = defaultValue()
)

class ExtractedRetainData {
    // All values that should be retained should appear in this class.
    // It's possible to manage a CoroutineScope using RetainObserver.
    // See the full sample for details.
    var retainedData = Any()
}

class CombinedRememberRetained(
    private val saveData: ExtractedSaveData,
    private val retainData: ExtractedRetainData,
) {
    fun doAction() {
        // Manipulate the retained and saved state as needed.
    }
}

By separating the state by lifespan, the separation of responsibilities and storage becomes very explicit. সেভ করা ডেটা যাতে রিটেন ডেটা দিয়ে ম্যানিপুলেট করা না যায়, সেই জন্য এটি করা হয়েছে। এর ফলে, savedInstanceState বান্ডেল ক্যাপচার করা হয়ে গেলে এবং আপডেট করা না গেলে, সেভ করা ডেটা আপডেট করার চেষ্টা করা হয় না। এছাড়াও, এটি Compose-এ কল না করে বা অ্যাক্টিভিটি রিক্রিয়েশন সিমুলেট না করে আপনার কনস্ট্রাক্টর টেস্ট করার মাধ্যমে রিক্রিয়েশন সিনারিও টেস্ট করার সুবিধা দেয়।

এই প্যাটার্ন কীভাবে প্রয়োগ করা যেতে পারে তার সম্পূর্ণ উদাহরণ দেখতে সম্পূর্ণ স্যাম্পেল (RetainAndSaveSample.kt) দেখুন।

পজিশনাল মেমোইজেশন ও অ্যাডাপ্টিভ লেআউট

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

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

ListDetailPaneScaffold এবং NavDisplay-এর মতো আউট-অফ-দ্য-বক্স কম্পোনেন্টের জন্য (Jetpack Navigation 3 থেকে), এটি কোনও সমস্যা নয় এবং লেআউট পরিবর্তন জুড়ে আপনার স্টেট অপরিবর্তিত থাকবে। ফর্ম ফ্যাক্টরের সাথে মানানসই কাস্টম কম্পোনেন্টের জন্য, নিশ্চিত করুন যে নিম্নলিখিতগুলির মধ্যে একটি করে লেআউট পরিবর্তনের ফলে স্টেট প্রভাবিত না হয়:

  • কম্পোজিশন হায়ারার্কিতে স্টেটফুল কম্পোজেবলগুলি যাতে সবসময় একই জায়গায় কল করা হয় তা নিশ্চিত করুন। কম্পোজিশন হায়ারার্কিতে অবজেক্ট রিলোকেট করার পরিবর্তে লেআউট লজিক পরিবর্তন করে অ্যাডাপ্টিভ লেআউট প্রয়োগ করা।
  • স্টেটফুল কম্পোজেবলকে সুন্দরভাবে রিলোকেট করতে MovableContent ব্যবহার করুন। MovableContent-এর ইনস্ট্যান্স মনে রাখা ও ধরে রাখা ভ্যালু পুরনো থেকে নতুন লোকেশনে সরাতে পারে।

ফ্যাক্টরি ফাংশন মনে রাখুন

Compose UI কম্পোজেবল ফাংশন দিয়ে তৈরি হলেও, কম্পোজিশন তৈরি ও সাজানোর জন্য অনেক অবজেক্ট ব্যবহার করা হয়। এর সবচেয়ে সাধারণ উদাহরণ হল জটিল কম্পোজ করার উপযুক্ত অবজেক্ট যা নিজের স্ট্যাটাসকে সংজ্ঞায়িত করে, যেমন LazyList, যা LazyListState গ্রহণ করে।

Compose-ফোকাসড অবজেক্ট সংজ্ঞায়িত করার সময়, আমরা সাজেস্ট করি যে আপনি একটি remember ফাংশন তৈরি করুন যাতে লাইফস্প্যান ও কী ইনপুট সহ অভিপ্রেত রিমেম্বারিং আচরণ সংজ্ঞায়িত করা যায়। এটি আপনার স্টেট কনজিউমারকে কম্পোজিশন হায়ারার্কিতে আত্মবিশ্বাসের সাথে ইনস্ট্যান্স তৈরি করতে দেয় যা প্রত্যাশা অনুযায়ী টিকে থাকবে এবং বাতিল হয়ে যাবে। কম্পোজেবল ফ্যাক্টরি ফাংশন সংজ্ঞায়িত করার সময়, এইসব নির্দেশিকা অনুসরণ করুন:

  • remember-এর সাথে ফাংশনের নামের প্রিফিক্স যোগ করুন। বিকল্প হিসেবে, ফাংশন ইমপ্লিমেন্টেশন যদি retained অবজেক্টের উপর নির্ভর করে এবং API যদি কখনও remember-এর অন্য কোনও ভেরিয়েশনের উপর নির্ভর করার জন্য পরিবর্তিত না হয়, তাহলে পরিবর্তে retain প্রিফিক্স ব্যবহার করুন।
  • স্টেট পারসিস্টেন্স বেছে নেওয়া হলে এবং সঠিক Saver ইমপ্লিমেন্টেশন লেখা সম্ভব হলে, rememberSaveable বা rememberSerializable ব্যবহার করুন।
  • CompositionLocal-এর উপর ভিত্তি করে সাইড এফেক্ট বা ভ্যালু ইনিশিয়ালাইজ করা এড়িয়ে চলুন, কারণ সেগুলি ব্যবহারের ক্ষেত্রে প্রাসঙ্গিক নাও হতে পারে। মনে রাখবেন, আপনার স্টেট যেখানে তৈরি করা হয়েছে সেখানে এটি ব্যবহার নাও করা হতে পারে।

@Composable
fun rememberImageState(
    imageUri: String,
    initialZoom: Float = 1f,
    initialPanX: Int = 0,
    initialPanY: Int = 0
): ImageState {
    return rememberSaveable(imageUri, saver = ImageState.Saver) {
        ImageState(
            imageUri, initialZoom, initialPanX, initialPanY
        )
    }
}

data class ImageState(
    val imageUri: String,
    val zoom: Float,
    val panX: Int,
    val panY: Int
) {
    object Saver : androidx.compose.runtime.saveable.Saver<ImageState, Any> by listSaver(
        save = { listOf(it.imageUri, it.zoom, it.panX, it.panY) },
        restore = { ImageState(it[0] as String, it[1] as Float, it[2] as Int, it[3] as Int) }
    )
}