কম্পোজে রাজ্যের আয়ুষ্কাল

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

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

সঠিক জীবনকাল বেছে নিন

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

remember

retain

rememberSaveable , rememberSerializable

মূল্যবোধ recompositions বেঁচে?

কার্যকলাপের বিনোদনের পরেও কি মূল্যবোধ টিকে থাকে?

সর্বদা একই ( === ) ইনস্ট্যান্স ফেরত দেওয়া হবে

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

প্রক্রিয়াগত মৃত্যু সত্ত্বেও মূল্যবোধ টিকে থাকে?

সমর্থিত ডেটা প্রকার

সব

অ্যাক্টিভিটিটি ধ্বংস হয়ে গেলে ফাঁস হয়ে যেতে পারে এমন কোনো অবজেক্টকে উল্লেখ করা যাবে না।

অবশ্যই ক্রমিকযোগ্য হতে হবে
(কাস্টম Saver অথবা kotlinx.serialization ব্যবহার করে)

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

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

remember

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

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

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

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

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

তবে, আপনার এড়িয়ে চলা উচিত:

  • 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 এমন সব ভ্যালুকে পারসিস্ট করতে পারে যেগুলোকে সিরিয়ালাইজ করা যায় না, যেমন ল্যাম্বডা এক্সপ্রেশন, ফ্লো এবং বিটম্যাপের মতো বড় অবজেক্ট। উদাহরণস্বরূপ, কনফিগারেশন পরিবর্তনের সময় মিডিয়া প্লেব্যাকে যাতে কোনো বাধা না আসে, তা নিশ্চিত করতে আপনি একটি মিডিয়া প্লেয়ার (যেমন ExoPlayer) পরিচালনা করার জন্য retain ব্যবহার করতে পারেন।

@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 স্টেট সংরক্ষণ এবং ব্যাকগ্রাউন্ড টাস্ক সম্পাদনের জন্য একটি ভালো জায়গা হিসেবে কাজ করে, সেখানে cache, impression tracking ও analytics, AndroidView এর উপর নির্ভরতা, এবং Android OS-এর সাথে ইন্টারঅ্যাক্ট করে বা পেমেন্ট প্রসেসর বা বিজ্ঞাপনের মতো থার্ড-পার্টি লাইব্রেরি retain করে এমন অন্যান্য অবজেক্ট সংরক্ষণের জন্য retain একটি ভালো বিকল্প।

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

retain

ViewModel

পরিধি নির্ধারণ

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

ViewModel গুলো একটি ViewModelStore অন্তর্গত সিঙ্গেলটন।

ধ্বংস

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

যখন ViewModelStore খালি বা ধ্বংস করা হয়

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

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

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

মালিকানাধীন

RetainedValuesStore

ViewModelStore

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

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

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

কখনও কখনও, একটি অবজেক্টের retained এবং rememberSaveable বা rememberSerializable উভয়ের সমন্বয়ে একটি হাইব্রিড লাইফস্প্যান থাকার প্রয়োজন হয়। এটি একটি ইঙ্গিত হতে পারে যে আপনার অবজেক্টটি একটি 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.
    }
}

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

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

অবস্থানগত মেমোইজেশন এবং অভিযোজিত লেআউট

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

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

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

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

কারখানার কার্যাবলী মনে রাখবেন

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

কম্পোজ-কেন্দ্রিক অবজেক্ট সংজ্ঞায়িত করার সময়, আমরা একটি 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) }
    )
}