আপনার স্টেট কোথায় হোইস্ট করা হচ্ছে এবং প্রয়োজনীয় লজিকের ওপর নির্ভর করে, আপনি আপনার UI স্টেট সংরক্ষণ ও পুনরুদ্ধার করতে বিভিন্ন API ব্যবহার করতে পারেন। প্রতিটি অ্যাপই এই কাজটি সর্বোত্তমভাবে সম্পন্ন করার জন্য একাধিক API-এর সমন্বয় ব্যবহার করে।
অ্যাক্টিভিটি বা প্রসেস পুনরায় চালু হওয়ার কারণে যেকোনো অ্যান্ড্রয়েড অ্যাপ তার UI স্টেট হারাতে পারে। নিম্নলিখিত ঘটনাগুলোর কারণে এই স্টেট নষ্ট হতে পারে:
- কনফিগারেশন পরিবর্তন । যদি না কনফিগারেশন পরিবর্তনটি ম্যানুয়ালি করা হয়, তাহলে অ্যাক্টিভিটিটি ধ্বংস হয়ে যায় এবং পুনরায় তৈরি হয়।
- সিস্টেম-প্রবর্তিত প্রসেস বন্ধ হওয়া । অ্যাপটি ব্যাকগ্রাউন্ডে থাকে এবং ডিভাইসটি অন্যান্য প্রসেসের ব্যবহারের জন্য রিসোর্স (যেমন মেমরি) খালি করে দেয়।
একটি ইতিবাচক ব্যবহারকারীর অভিজ্ঞতার জন্য এই ঘটনাগুলোর পরে স্টেট সংরক্ষণ করা অপরিহার্য। কোন স্টেটটি সংরক্ষণ করা হবে তা আপনার অ্যাপের স্বতন্ত্র ইউজার ফ্লো-এর উপর নির্ভর করে। একটি উত্তম অনুশীলন হিসেবে, আপনার অন্তত ব্যবহারকারীর ইনপুট এবং নেভিগেশন-সম্পর্কিত স্টেট সংরক্ষণ করা উচিত। এর উদাহরণগুলোর মধ্যে রয়েছে একটি তালিকার স্ক্রোল পজিশন, যে আইটেমটি সম্পর্কে ব্যবহারকারী আরও বিস্তারিত জানতে চান তার আইডি, ব্যবহারকারীর পছন্দের চলমান নির্বাচন, বা টেক্সট ফিল্ডে দেওয়া ইনপুট।
আপনার স্টেট কোথায় হোইস্ট করা হচ্ছে এবং কোন লজিকের জন্য এটি প্রয়োজন, তার উপর নির্ভর করে UI স্টেট সংরক্ষণের জন্য উপলব্ধ API-গুলো এই পৃষ্ঠায় সংক্ষেপে তুলে ধরা হয়েছে।
UI লজিক
যদি আপনার স্টেট UI-তে হোইস্ট করা থাকে, তা কম্পোজেবল ফাংশনেই হোক বা কম্পোজিশনের আওতাভুক্ত সাধারণ স্টেট হোল্ডার ক্লাসের মধ্যেই হোক, আপনি অ্যাক্টিভিটি এবং প্রসেস পুনঃসৃষ্টির পরেও স্টেট ধরে রাখতে rememberSaveable ব্যবহার করতে পারেন।
নিম্নলিখিত কোড অংশে, একটিমাত্র বুলিয়ান UI এলিমেন্টের অবস্থা সংরক্ষণ করতে rememberSaveable ব্যবহার করা হয়েছে:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } ClickableText( text = AnnotatedString(message.content), onClick = { showDetails = !showDetails } ) if (showDetails) { Text(message.timestamp) } }
showDetails হলো একটি বুলিয়ান ভেরিয়েবল যা চ্যাট বাবলটি সংকুচিত নাকি প্রসারিত তা সংরক্ষণ করে।
rememberSaveable সেভড ইনস্ট্যান্স স্টেট মেকানিজমের মাধ্যমে UI এলিমেন্টের স্টেট একটি Bundle সংরক্ষণ করে।
এটি স্বয়ংক্রিয়ভাবে বান্ডেলে প্রিমিটিভ টাইপ সংরক্ষণ করতে সক্ষম। যদি আপনার স্টেট কোনো নন-প্রিমিটিভ টাইপে, যেমন একটি ডেটা ক্লাসে, সংরক্ষিত থাকে, তবে আপনি বিভিন্ন সংরক্ষণ পদ্ধতি ব্যবহার করতে পারেন, যেমন Parcelize অ্যানোটেশন ব্যবহার করা, listSaver এবং mapSaver এর মতো Compose API ব্যবহার করা, অথবা Compose রানটাইম Saver ক্লাসকে এক্সটেন্ড করে একটি কাস্টম সেভার ক্লাস ইমপ্লিমেন্ট করা। এই পদ্ধতিগুলো সম্পর্কে আরও জানতে "Ways to store state" ডকুমেন্টেশনটি দেখুন।
নিম্নলিখিত কোড স্নিপেটে, rememberLazyListState Compose API-টি rememberSaveable ব্যবহার করে LazyListState সংরক্ষণ করে, যা একটি LazyColumn বা LazyRow এর স্ক্রল স্টেট নিয়ে গঠিত। এটি একটি LazyListState.Saver ব্যবহার করে, যা একটি কাস্টম সেভার এবং স্ক্রল স্টেট সংরক্ষণ ও পুনরুদ্ধার করতে সক্ষম। কোনো অ্যাক্টিভিটি বা প্রসেস পুনরায় চালু হওয়ার পর (উদাহরণস্বরূপ, ডিভাইসের ওরিয়েন্টেশন পরিবর্তনের মতো কোনো কনফিগারেশন পরিবর্তনের পরে), স্ক্রল স্টেটটি সংরক্ষিত থাকে।
@Composable fun rememberLazyListState( initialFirstVisibleItemIndex: Int = 0, initialFirstVisibleItemScrollOffset: Int = 0 ): LazyListState { return rememberSaveable(saver = LazyListState.Saver) { LazyListState( initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset ) } }
সর্বোত্তম অনুশীলন
rememberSaveable UI স্টেট সংরক্ষণ করার জন্য একটি Bundle ব্যবহার করে, যা আপনার অ্যাক্টিভিটির onSaveInstanceState() কলের মতো অন্যান্য এপিআই-এর সাথেও শেয়ার করা হয়, যারা এতে ডেটা লেখে। তবে, এই Bundle আকার সীমিত, এবং বড় আকারের অবজেক্ট সংরক্ষণ করলে রানটাইমে TransactionTooLarge এক্সেপশন দেখা দিতে পারে। এটি বিশেষ করে একক Activity অ্যাপগুলিতে সমস্যাজনক হতে পারে, যেখানে পুরো অ্যাপ জুড়ে একই Bundle ব্যবহৃত হয়।
এই ধরনের ক্র্যাশ এড়াতে, আপনার বান্ডেলে বড় ও জটিল অবজেক্ট অথবা অবজেক্টের তালিকা সংরক্ষণ করা উচিত নয় ।
এর পরিবর্তে, আইডি বা কী-এর মতো ন্যূনতম প্রয়োজনীয় স্টেট সংরক্ষণ করুন এবং এগুলি ব্যবহার করে আরও জটিল UI স্টেট পুনরুদ্ধারের কাজটি পারসিস্টেন্ট স্টোরেজের মতো অন্যান্য পদ্ধতির ওপর অর্পণ করুন।
এই ডিজাইন পছন্দগুলো আপনার অ্যাপের নির্দিষ্ট ব্যবহারের ক্ষেত্র এবং ব্যবহারকারীরা এর থেকে কেমন আচরণ আশা করেন, তার উপর নির্ভর করে।
অবস্থা পুনরুদ্ধার যাচাই করুন
অ্যাক্টিভিটি বা প্রসেসটি পুনরায় তৈরি করার সময়, আপনার Compose এলিমেন্টগুলিতে rememberSaveable এর মাধ্যমে সংরক্ষিত স্টেটটি সঠিকভাবে পুনরুদ্ধার হচ্ছে কিনা, তা আপনি যাচাই করতে পারেন। এটি করার জন্য নির্দিষ্ট API রয়েছে, যেমন StateRestorationTester । আরও জানতে টেস্টিং ডকুমেন্টেশন দেখুন।
ব্যবসায়িক যুক্তি
যদি ব্যবসায়িক যুক্তির প্রয়োজনে আপনার UI এলিমেন্টের স্টেট ViewModel এ হোইস্ট করা হয়, তাহলে আপনি ViewModel এর API-গুলো ব্যবহার করতে পারেন।
আপনার অ্যান্ড্রয়েড অ্যাপ্লিকেশনে ViewModel ব্যবহারের অন্যতম প্রধান সুবিধা হলো এটি বিনামূল্যে কনফিগারেশন পরিবর্তন পরিচালনা করে। যখন কোনো কনফিগারেশন পরিবর্তন হয় এবং অ্যাক্টিভিটিটি ডেস্ট্রয় ও পুনরায় তৈরি করা হয়, তখন ViewModel এ হোইস্ট করা UI স্টেট মেমরিতে সংরক্ষিত থাকে। পুনরায় তৈরির পর, পুরোনো ViewModel ইনস্ট্যান্সটি নতুন অ্যাক্টিভিটি ইনস্ট্যান্সের সাথে অ্যাটাচ হয়ে যায়।
তবে, সিস্টেম-জনিত প্রসেস বন্ধ হয়ে গেলে একটি ViewModel ইনস্ট্যান্স টিকে থাকে না। এর ফলে UI স্টেট যাতে টিকে থাকে, তার জন্য ViewModel-এর Saved State মডিউলটি ব্যবহার করুন, যেটিতে SavedStateHandle API-টি রয়েছে।
সর্বোত্তম অনুশীলন
SavedStateHandle UI স্টেট সংরক্ষণ করতে Bundle মেকানিজম ব্যবহার করে, তাই এটি শুধুমাত্র সাধারণ UI এলিমেন্টের স্টেট সংরক্ষণের জন্য ব্যবহার করা উচিত।
স্ক্রিন UI স্টেট , যা বিজনেস রুল প্রয়োগ করে এবং UI ছাড়া আপনার অ্যাপ্লিকেশনের অন্যান্য লেয়ার অ্যাক্সেস করার মাধ্যমে তৈরি হয়, তা এর সম্ভাব্য জটিলতা এবং আকারের কারণে SavedStateHandle এ সংরক্ষণ করা উচিত নয়। জটিল বা বড় ডেটা সংরক্ষণের জন্য আপনি বিভিন্ন পদ্ধতি ব্যবহার করতে পারেন, যেমন লোকাল পারসিস্টেন্ট স্টোরেজ । একটি প্রসেস পুনরায় চালু হওয়ার পর, SavedStateHandle এ সংরক্ষিত (যদি থাকে) পুনরুদ্ধার করা ট্রানজিয়েন্ট স্টেট দিয়ে স্ক্রিনটি পুনরায় তৈরি করা হয় এবং ডেটা লেয়ার থেকে স্ক্রিন UI স্টেট আবার তৈরি করা হয়।
SavedStateHandle এপিআই
SavedStateHandle UI এলিমেন্টের স্টেট সংরক্ষণ করার জন্য বিভিন্ন API রয়েছে, যার মধ্যে উল্লেখযোগ্য হলো:
রচনা State | saveable() |
|---|---|
StateFlow | getStateFlow() |
রচনা State
SavedStateHandle এর saveable API ব্যবহার করে UI এলিমেন্টের স্টেটকে MutableState হিসেবে পড়া ও লেখা যায়, ফলে ন্যূনতম কোড সেটআপের মাধ্যমেই এটি অ্যাক্টিভিটি এবং প্রসেস পুনঃসৃষ্টির পরেও টিকে থাকে।
rememberSaveable() মতোই, saveable API স্বয়ংক্রিয়ভাবে প্রিমিটিভ টাইপ সমর্থন করে এবং কাস্টম সেভার ব্যবহারের জন্য একটি stateSaver প্যারামিটার গ্রহণ করে।
নিম্নলিখিত কোড স্নিপেটে, message একটি TextField ব্যবহারকারীর টাইপ করা ইনপুট সংরক্ষণ করে:
class ConversationViewModel( savedStateHandle: SavedStateHandle ) : ViewModel() { var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue("")) } private set fun update(newMessage: TextFieldValue) { message = newMessage } /*...*/ } val viewModel = ConversationViewModel(SavedStateHandle()) @Composable fun UserInput(/*...*/) { TextField( value = viewModel.message, onValueChange = { viewModel.update(it) } ) }
saveable API ব্যবহারের বিষয়ে আরও তথ্যের জন্য SavedStateHandle ডকুমেন্টেশন দেখুন।
StateFlow
UI এলিমেন্টের স্টেট সংরক্ষণ করতে এবং SavedStateHandle থেকে ফ্লো হিসেবে তা ব্যবহার করতে getStateFlow() ব্যবহার করুন। StateFlow টি রিড-অনলি, এবং API-টির জন্য আপনাকে একটি কী (key) নির্দিষ্ট করতে হবে, যাতে আপনি ফ্লো-টি প্রতিস্থাপন করে একটি নতুন ভ্যালু নির্গত করতে পারেন। আপনার কনফিগার করা কী-টি দিয়ে, আপনি StateFlow টি পুনরুদ্ধার করতে এবং সর্বশেষ ভ্যালুটি সংগ্রহ করতে পারবেন।
নিম্নলিখিত কোড স্নিপেটে, savedFilterType হলো একটি StateFlow ভেরিয়েবল যা একটি চ্যাট অ্যাপের চ্যাট চ্যানেলগুলির তালিকায় প্রয়োগ করা ফিল্টার টাইপ সংরক্ষণ করে:
private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey" class ChannelViewModel( channelsRepository: ChannelsRepository, private val savedStateHandle: SavedStateHandle ) : ViewModel() { private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow( key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS ) private val filteredChannels: Flow<List<Channel>> = combine(channelsRepository.getAll(), savedFilterType) { channels, type -> filter(channels, type) }.onStart { emit(emptyList()) } fun setFiltering(requestType: ChannelsFilterType) { savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType } /*...*/ } enum class ChannelsFilterType { ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS }
প্রতিবার ব্যবহারকারী একটি নতুন ফিল্টার টাইপ নির্বাচন করলে, setFiltering কল করা হয়। এটি _CHANNEL_FILTER_SAVED_STATE_KEY_ কী-সহ সংরক্ষিত SavedStateHandle এ একটি নতুন মান সংরক্ষণ করে। savedFilterType হলো একটি ফ্লো যা কী-তে সংরক্ষিত সর্বশেষ মানটি নির্গত করে। চ্যানেল ফিল্টারিং সম্পাদন করার জন্য filteredChannels ফ্লো-টিতে সাবস্ক্রাইব করা থাকে।
getStateFlow() API সম্পর্কে আরও তথ্যের জন্য SavedStateHandle ডকুমেন্টেশন দেখুন।
সারসংক্ষেপ
নিম্নলিখিত সারণিতে এই বিভাগে আলোচিত API-গুলো এবং UI স্টেট সংরক্ষণ করার জন্য কখন কোনটি ব্যবহার করতে হবে তা সংক্ষেপে তুলে ধরা হলো:
| অনুষ্ঠান | UI লজিক | ViewModel এ ব্যবসায়িক যুক্তি |
|---|---|---|
| কনফিগারেশন পরিবর্তন | rememberSaveable | স্বয়ংক্রিয় |
| সিস্টেম-প্রবর্তিত প্রক্রিয়া মৃত্যু | rememberSaveable | SavedStateHandle |
কোন এপিআই ব্যবহার করতে হবে তা নির্ভর করে স্টেটটি কোথায় রাখা আছে এবং এর জন্য কী লজিক প্রয়োজন তার উপর। যে স্টেট UI লজিকে ব্যবহৃত হয়, তার জন্য rememberSaveable ব্যবহার করুন। আর যে স্টেট বিজনেস লজিকে ব্যবহৃত হয়, সেটি যদি একটি ViewModel এ রাখা থাকে, তবে SavedStateHandle ব্যবহার করে সেভ করুন।
অল্প পরিমাণে UI স্টেট সংরক্ষণ করার জন্য আপনার বান্ডেল API ( rememberSaveable এবং SavedStateHandle ) ব্যবহার করা উচিত। অন্যান্য সংরক্ষণ পদ্ধতির সাথে UI-কে তার পূর্বের অবস্থায় ফিরিয়ে আনার জন্য এই ডেটা হলো ন্যূনতম প্রয়োজনীয়। উদাহরণস্বরূপ, যদি আপনি ব্যবহারকারীর দেখা কোনো প্রোফাইলের আইডি বান্ডেলে সংরক্ষণ করেন, তাহলে আপনি ডেটা লেয়ার থেকে প্রোফাইলের বিবরণের মতো ভারী ডেটা সংগ্রহ করতে পারবেন।
UI স্টেট সংরক্ষণের বিভিন্ন পদ্ধতি সম্পর্কে আরও তথ্যের জন্য, সাধারণ 'Saving UI State' ডকুমেন্টেশন এবং আর্কিটেকচার গাইডের ডেটা লেয়ার পৃষ্ঠাটি দেখুন।
{% হুবহু %}আপনার জন্য প্রস্তাবিত
- দ্রষ্টব্য: জাভাস্ক্রিপ্ট বন্ধ থাকলেও লিঙ্কের লেখা প্রদর্শিত হয়।
- কোথায় রাজ্য উত্তোলন করতে হবে
- স্টেট এবং জেটপ্যাক কম্পোজ
- তালিকা এবং গ্রিড