Compose অ্যাপ্লিকেশনে, UI লজিক বা বিজনেস লজিকের প্রয়োজন আছে কিনা তার উপর নির্ভর করে UI স্টেট হোয়েস্ট করা হয়। এই ডকুমেন্টে এই দুটি প্রধান পরিস্থিতি বর্ণনা করা হয়েছে।
পেশাদার পদ্ধতি
যেসব কম্পোজ করার মতো আইটেম UI স্টেট রিড ও রাইট করে, সেগুলির মধ্যে সবচেয়ে ছোট সাধারণ পূর্বপুরুষ-এ আপনাকে UI স্টেট হোয়েস্ট করতে হবে। যেখানে এটি ব্যবহার করা হয়, তার সবচেয়ে কাছাকাছি স্টেট রাখতে হবে। স্টেট মালিকের থেকে, অপরিবর্তনীয় স্টেট ও ইভেন্ট কনজিউমারদের কাছে প্রকাশ করুন স্টেট পরিবর্তন করতে।
সবচেয়ে সাধারণ পূর্বপুরুষ কম্পোজিশনের বাইরেও থাকতে পারে। যেমন,
ViewModel-এ স্টেট হোয়েস্ট করার সময়, কারণ এতে বিজনেস লজিক যুক্ত থাকে।
এই পৃষ্ঠায় এই পেশাদার পদ্ধতি বিস্তারিতভাবে ব্যাখ্যা করা হয়েছে এবং একটি সতর্কতাও দেওয়া হয়েছে যা মনে রাখতে হবে।
UI স্টেট ও UI লজিকের ধরন
এই ডকুমেন্টে ব্যবহৃত UI স্টেট ও লজিকের ধরন সম্পর্কিত সংজ্ঞা নিচে দেওয়া হল।
UI স্টেট
UI স্টেট হল এমন একটি প্রপার্টি যা UI-কে বর্ণনা করে। UI স্টেট দুই ধরনের হয়:
- স্ক্রিন UI স্টেট হল যা আপনাকে স্ক্রিনে দেখাতে হবে। যেমন,
UI রেন্ডার করার জন্য প্রয়োজনীয়
খবরের নিবন্ধ ও অন্যান্য তথ্য
NewsUiStateক্লাসে থাকতে পারে। এই স্টেট সাধারণত হায়ারার্কির অন্যান্য লেয়ারের সাথে কানেক্ট করা থাকে কারণ এতে অ্যাপ ডেটা থাকে। - UI এলিমেন্টের স্টেট বলতে UI এলিমেন্টের অন্তর্নিহিত প্রপার্টিকে বোঝায় যা
সেগুলি কীভাবে রেন্ডার করা হয় তা প্রভাবিত করে। UI এলিমেন্ট দেখানো বা লুকানো হতে পারে এবং এতে
নির্দিষ্ট ফন্ট, ফন্ট সাইজ বা ফন্ট কালার থাকতে পারে। Jetpack
Compose-এ, কম্পোজ করার উপযুক্ত আইটেমের বাইরে স্টেট থাকে এবং আপনি এমনকি এটিকে
কম্পোজ করার উপযুক্ত আইটেমের ঠিক কাছাকাছি থেকে কলিং কম্পোজ করার উপযুক্ত আইটেম
ফাংশন বা স্টেট হোল্ডারে হোইস্ট করতে পারেন। এর একটি উদাহরণ হল
ScaffoldStateযাScaffoldকম্পোজ করার উপযুক্ত।
লজিক
কোনও অ্যাপ্লিকেশনের লজিক, বিজনেস লজিক বা UI লজিক হতে পারে:
- বিজনেস লজিক হল অ্যাপ ডেটার জন্য প্রোডাক্টের প্রয়োজনীয়তা প্রয়োগ করা। যেমন, ব্যবহারকারী বোতামে ট্যাপ করলে, কোনও নিউজ রিডার অ্যাপে কোনও নিবন্ধ বুকমার্ক করা। ফাইল বা ডেটাবেসে বুকমার্ক সেভ করার এই লজিক সাধারণত ডোমেন বা ডেটা লেয়ারে প্লেস করা হয়। স্টেট হোল্ডার সাধারণত সেইসব লেয়ারের কাছে এই লজিক ডেলিগেট করে, যেগুলি তারা এক্সপোজ করে এমন মেথড কল করে।
- UI লজিক স্ক্রিনে UI স্টেট কীভাবে দেখানো হবে তার সাথে সম্পর্কিত। যেমন, ব্যবহারকারী কোনও বিভাগ বেছে নিলে সঠিক সার্চ বার ইঙ্গিত পাওয়া, কোনও তালিকায় নির্দিষ্ট আইটেমে স্ক্রল করা অথবা ব্যবহারকারী কোনও বোতামে ক্লিক করলে নির্দিষ্ট স্ক্রিনে নেভিগেট করার লজিক।
UI লজিক
UI লজিক-কে স্টেট রিড বা রাইট করতে হলে, আপনাকে UI-এর লাইফসাইকেল মেনে স্টেটকে UI-এর মধ্যে স্কোপ করতে হবে। এটি করতে, আপনাকে কম্পোজ করার উপযুক্ত ফাংশনে সঠিক লেভেলে স্টেট হোইস্ট করতে হবে। বিকল্প হিসেবে, আপনি সাধারণ স্টেট হোল্ডার ক্লাসে এটি করতে পারেন, এটিও UI লাইফসাইকেলে স্কোপ করা হয়।
নিচে দুটি সমাধান সম্পর্কে বিবরণ দেওয়া হল এবং কখন কোনটি ব্যবহার করতে হবে সেই সম্পর্কে ব্যাখ্যা করা হল।
স্টেট মালিক হিসেবে কম্পোজ করার উপযুক্ত
UI লজিক ও UI এলিমেন্ট স্টেট কম্পোজ করার উপযুক্ত হলে, তা একটি ভালো পদ্ধতি হিসেবে বিবেচিত হয়। তবে স্টেট ও লজিক সহজ হতে হবে। প্রয়োজন অনুযায়ী, আপনি নিজের স্টেটকে কম্পোজ করা যায় এমন আইটেমের ইন্টার্নাল হিসেবে ছেড়ে দিতে পারেন অথবা হোইস্ট করতে পারেন।
কোনও স্টেট হোয়েস্টিংয়ের প্রয়োজন নেই
হোস্টিং স্টেট সবসময় প্রয়োজন হয় না। অন্য কোনও কম্পোজ করার মতো আইটেমকে কন্ট্রোল করার প্রয়োজন না হলে, কম্পোজ করার মতো আইটেমে স্টেট ইন্টার্নাল রাখা যেতে পারে। এই স্নিপেটে, একটি কম্পোজ করার উপযুক্ত আইটেম আছে যা ট্যাপ করলে বড় ও ছোট হয়:
@Composable fun ChatBubble( message: Message ) { var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state Text( text = AnnotatedString(message.content), modifier = Modifier.clickable { showDetails = !showDetails // Apply UI logic } ) if (showDetails) { Text(message.timestamp) } }
showDetails ভেরিয়েবল হল এই UI এলিমেন্টের ইন্টার্নাল স্টেট। এটি শুধুমাত্র এই কম্পোজ করার উপযুক্ত আইটেমে
পড়া ও পরিবর্তন করা হয় এবং এতে প্রয়োগ করা লজিক খুবই সহজ।
এই ক্ষেত্রে স্টেট হোয়েস্ট করলে খুব বেশি সুবিধা পাওয়া যাবে না, তাই আপনি
এটি ইন্টার্নাল রাখতে পারেন। এটি করলে, এই কম্পোজ করার উপযুক্ত আইটেমটি এক্সপ্যান্ড করা স্টেটের মালিক এবং একমাত্র
সত্যের সোর্স হয়ে যায়।
কম্পোজেবল ফাংশনের মধ্যে হোস্টিং
অন্যান্য কম্পোজ করার উপযুক্ত আইটেমের সাথে UI এলিমেন্টের স্টেট শেয়ার করতে এবং বিভিন্ন জায়গায় UI লজিক প্রয়োগ করতে হলে, UI হায়ারার্কিতে এটিকে আরও উপরে হোয়েস্ট করতে পারেন। এছাড়াও, এটি আপনার কম্পোজেবলকে আরও বেশি করে আবার ব্যবহারযোগ্য এবং পরীক্ষা করা সহজ করে তোলে।
নিচে দেওয়া উদাহরণ হল একটি চ্যাট অ্যাপ যা দুটি ফাংশনালিটি প্রয়োগ করে:
JumpToBottomবোতামটি মেসেজের তালিকা স্ক্রল করে একেবারে নিচে নিয়ে যায়। বোতামটি তালিকার স্ট্যাটাসে UI লজিক পারফর্ম করে।- ব্যবহারকারী নতুন মেসেজ পাঠানোর পরে
MessagesListতালিকাটি স্ক্রল করে নিচে চলে আসে। UserInput, তালিকার স্টেটে UI লজিক পারফর্ম করে।
JumpToBottom বোতাম সহ চ্যাট অ্যাপ এবং নতুন মেসেজে স্ক্রল করে নিচে যাওয়াকম্পোজ করা যায় এমন হায়ারার্কি নিচে দেওয়া হল:
LazyColumn স্টেটটি কথোপকথনের স্ক্রিনে হোয়েস্ট করা হয় যাতে অ্যাপটি
UI লজিক পারফর্ম করতে এবং প্রয়োজনীয় সব কম্পোজ করার উপযুক্ত আইটেম থেকে স্টেট পড়তে পারে:
LazyColumn থেকে ConversationScreenLazyColumn স্টেট হোয়েস্টিং করা
তাই, কম্পোজ করার উপযুক্ত আইটেমগুলি হল:
LazyListState সহ চ্যাট কম্পোজ করার উপযুক্ত ট্রি ConversationScreenকোডটি হল:
@Composable private fun ConversationScreen(/*...*/) { val scope = rememberCoroutineScope() val lazyListState = rememberLazyListState() // State hoisted to the ConversationScreen MessagesList(messages, lazyListState) // Reuse same state in MessageList UserInput( onMessageSent = { // Apply UI logic to lazyListState scope.launch { lazyListState.scrollToItem(0) } }, ) } @Composable private fun MessagesList( messages: List<Message>, lazyListState: LazyListState = rememberLazyListState() // LazyListState has a default value ) { LazyColumn( state = lazyListState // Pass hoisted state to LazyColumn ) { items(messages, key = { message -> message.id }) { item -> Message(/*...*/) } } val scope = rememberCoroutineScope() JumpToBottom(onClicked = { scope.launch { lazyListState.scrollToItem(0) // UI logic being applied to lazyListState } }) }
LazyListState-কে UI লজিকের জন্য যতটা উঁচুতে হোইস্ট করতে হয়, ততটা উঁচুতে হোইস্ট করা হয়, যা প্রয়োগ করতে
হয়। যেহেতু এটি একটি কম্পোজ করার উপযুক্ত ফাংশনে ইনিশিয়ালাইজ করা হয়, তাই এটি
কম্পোজিশনে স্টোর করা হয় এবং এর লাইফসাইকেল অনুসরণ করে।
মনে রাখবেন, lazyListState, MessagesList পদ্ধতিতে সংজ্ঞায়িত করা হয়েছে, যার
ডিফল্ট ভ্যালু হল rememberLazyListState()। Compose-এ এটি একটি সাধারণ প্যাটার্ন।
এটি কম্পোজ করার উপযুক্ত আইটেমকে আরও বেশি করে আবার ব্যবহারযোগ্য ও নমনীয় করে তোলে। তারপরে, আপনি অ্যাপের বিভিন্ন অংশে কম্পোজ করার উপযুক্ত
ব্যবহার করতে পারবেন যেগুলির স্টেট কন্ট্রোল করার প্রয়োজন নাও হতে পারে। কম্পোজ করার মতো আইটেম টেস্ট বা প্রিভিউ করার সময়
সাধারণত এমনটা ঘটে। ঠিক এইভাবেই
LazyColumn নিজের স্টেটকে সংজ্ঞায়িত করে।
LazyListState-এর জন্য সর্বনিম্ন সাধারণ পূর্বপুরুষ হল ConversationScreenস্টেট মালিক হিসেবে সাধারণ স্টেট হোল্ডার ক্লাস
কোনও কম্পোজ করার উপযুক্ত আইটেমে জটিল UI লজিক থাকলে, যার মধ্যে UI এলিমেন্টের এক বা একাধিক স্টেট ফিল্ড থাকে, তাহলে সেটিকে স্টেট হোল্ডার-এর কাছে সেই দায়িত্ব অর্পণ করতে হবে, যেমন সাধারণ স্টেট হোল্ডার ক্লাস। এর ফলে কম্পোজ করার উপযুক্ত আইটেমের লজিক আলাদাভাবে আরও সহজে পরীক্ষা করা যায় এবং এর জটিলতা কমে যায়। এই পদ্ধতিতে আলাদা আলাদা কাজ করার নীতি মেনে চলা হয়: কম্পোজ করার উপযুক্ত আইটেম UI এলিমেন্ট এমিট করার দায়িত্ব নেয় এবং স্টেট হোল্ডারে UI লজিক ও UI এলিমেন্ট স্টেট থাকে।
প্লেইন স্টেট হোল্ডার ক্লাস আপনার কম্পোজ করার উপযুক্ত ফাংশনের কলারদের জন্য সুবিধাজনক ফাংশন প্রদান করে, যাতে তাদের এই লজিক নিজে থেকে লিখতে না হয়।
এইসব সাধারণ ক্লাস কম্পোজিশনে তৈরি ও মনে রাখা হয়। এগুলি কম্পোজ করার মতো আইটেমের লাইফসাইকেল মেনে চলে বলে, Compose লাইব্রেরির দেওয়া rememberNavController() বা rememberLazyListState()-এর মতো
টাইপ নিতে পারে।
এর একটি উদাহরণ হল LazyListState প্লেন স্টেট হোল্ডার
ক্লাস, যা LazyColumn
বা LazyRow-এর UI জটিলতা নিয়ন্ত্রণ করতে Compose-এ প্রয়োগ করা হয়।
// LazyListState.kt @Stable class LazyListState constructor( firstVisibleItemIndex: Int = 0, firstVisibleItemScrollOffset: Int = 0 ) : ScrollableState { /** * The holder class for the current scroll position. */ private val scrollPosition = LazyListScrollPosition( firstVisibleItemIndex, firstVisibleItemScrollOffset ) suspend fun scrollToItem(/*...*/) { /*...*/ } override suspend fun scroll() { /*...*/ } suspend fun animateScrollToItem() { /*...*/ } }
LazyListState, LazyColumn-এর স্টেটকে এনক্যাপসুলেট করে এবং এই UI এলিমেন্টের জন্য
scrollPosition সেভ করে। এছাড়াও, এটি স্ক্রল পজিশন পরিবর্তন করার পদ্ধতিও প্রকাশ করে।
যেমন, কোনও নির্দিষ্ট আইটেমে স্ক্রল করা।
আপনি যেমন দেখতে পাচ্ছেন, কম্পোজ করার উপযুক্ত আইটেমের দায়িত্ব বৃদ্ধি করলে স্টেট হোল্ডারের প্রয়োজনীয়তা বেড়ে যায়। এই দায়িত্ব UI লজিক বা শুধু ট্র্যাক রাখার জন্য স্টেটের পরিমাণের মধ্যে থাকতে পারে।
আরেকটি সাধারণ প্যাটার্ন হল, অ্যাপের রুট কম্পোজ করার মতো ফাংশনের জটিলতা ম্যানেজ করার জন্য একটি সাধারণ স্টেট হোল্ডার ক্লাস ব্যবহার করা। আপনি নেভিগেশন স্টেট ও স্ক্রিন সাইজিংয়ের মতো অ্যাপ-লেভেল স্টেট এনক্যাপসুলেট করার জন্য এই ধরনের ক্লাস ব্যবহার করতে পারেন। UI লজিক ও এর স্টেট হোল্ডার পৃষ্ঠায় এর সম্পূর্ণ বিবরণ পাওয়া যাবে।
বিজনেস লজিক
কম্পোজ করার উপযুক্ত ও সাধারণ স্টেট হোল্ডার ক্লাস যদি UI লজিক ও UI এলিমেন্ট স্টেটের দায়িত্ব নেয়, তাহলে স্ক্রিন লেভেল স্টেট হোল্ডার নিম্নলিখিত টাস্কের দায়িত্ব নেয়:
- অ্যাপ্লিকেশনের বিজনেস লজিক অ্যাক্সেস করার সুবিধা প্রদান করা যা সাধারণত হায়ারার্কির অন্যান্য লেয়ারে যেমন বিজনেস ও ডেটা লেয়ারে থাকে।
- নির্দিষ্ট স্ক্রিনে প্রেজেন্ট করার জন্য অ্যাপ্লিকেশন ডেটা প্রস্তুত করা, যা স্ক্রিন UI স্টেট হয়ে যায়।
ViewModel-কে স্টেট মালিক হিসেবে ব্যবহার করা
Android ডেভেলপমেন্টে AAC ViewModels-এর সুবিধা এটিকে বিজনেস লজিক অ্যাক্সেস প্রদান এবং স্ক্রিনে প্রেজেন্ট করার জন্য অ্যাপ্লিকেশন ডেটা প্রস্তুত করার জন্য উপযুক্ত করে তোলে।
ViewModel-এ UI স্টেট হোয়েস্ট করলে, আপনি এটিকে কম্পোজিশনের
বাইরে সরিয়ে দেন।
ViewModel-এ হোইস্ট করা স্টেট কম্পোজিশনের বাইরে স্টোর করা হয়।ViewModel কম্পোজিশনের অংশ হিসেবে স্টোর করা হয় না। এগুলি ফ্রেমওয়ার্কের
মাধ্যমে প্রদান করা হয় এবং এগুলি ViewModelStoreOwner-এর স্কোপে থাকে, যা
অ্যাক্টিভিটি, ফ্র্যাগমেন্ট, নেভিগেশন গ্রাফ বা নেভিগেশন গ্রাফের ডেস্টিনেশন হতে পারে। ViewModel স্কোপ সম্পর্কে
আরও তথ্য পেতে, আপনি ডকুমেন্টেশন পর্যালোচনা করতে পারেন।
তারপর, ViewModel হল সোর্স অফ ট্রুথ এবং UI স্টেটের জন্য সবচেয়ে ছোট সাধারণ পূর্বপুরুষ।
স্ক্রিন UI স্টেট
উপরে দেওয়া সংজ্ঞা অনুযায়ী, স্ক্রিন UI স্টেট, বিজনেস
নিয়ম প্রয়োগ করে তৈরি করা হয়। যেহেতু স্ক্রিন লেভেল স্টেট হোল্ডার এর জন্য দায়ী, তাই এর অর্থ হল স্ক্রিন UI স্টেট সাধারণত স্ক্রিন লেভেল স্টেট
হোল্ডারে হোইস্ট করা হয়, এই ক্ষেত্রে একটি ViewModel।
চ্যাট অ্যাপের ConversationViewModel এবং কীভাবে এটি স্ক্রিন
UI স্টেট ও ইভেন্টকে পরিবর্তন করার জন্য এক্সপোজ করে তা বিবেচনা করুন:
class ConversationViewModel( channelId: String, messagesRepository: MessagesRepository ) : ViewModel() { val messages = messagesRepository .getLatestMessages(channelId) .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) // Business logic fun sendMessage(message: Message) { /* ... */ } }
কম্পোজ করার উপযুক্ত আইটেম ViewModel-এ হোয়েস্ট করা স্ক্রিন UI স্টেট ব্যবহার করে। বিজনেস লজিক অ্যাক্সেস করার
সুযোগ দিতে, আপনার স্ক্রিন-লেভেল কম্পোজেবল কোডে ViewModel ইনস্ট্যান্স ইনজেক্ট
করতে হবে।
নিচে স্ক্রিন-লেভেল কম্পোজ করার উপযুক্ত আইটেমে ব্যবহৃত ViewModel-এর একটি উদাহরণ দেওয়া হল।
এখানে, কম্পোজ করার উপযুক্ত ConversationScreen(), ViewModel-এ হোয়েস্ট করা স্ক্রিন UI স্টেট
ব্যবহার করে:
@Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val messages by conversationViewModel.messages.collectAsStateWithLifecycle() ConversationScreen( messages = messages, onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) } ) } @Composable private fun ConversationScreen( messages: List<Message>, onSendMessage: (Message) -> Unit ) { MessagesList(messages, onSendMessage) /* ... */ }
প্রপার্টি ড্রিলিং
“প্রপার্টি ড্রিলিং” বলতে ডেটা রিড করার লোকেশনে পৌঁছানোর জন্য সেটিকে একাধিক নেস্টেড চাইল্ড কম্পোনেন্টের মাধ্যমে পাস করাকে বোঝায়।
Compose-এ প্রপার্টি ড্রিলিং কোথায় দেখা যেতে পারে তার একটি সাধারণ উদাহরণ হল, আপনি যখন সবচেয়ে উপরের লেভেলে স্ক্রিন লেভেল স্টেট হোল্ডার ইনজেক্ট করেন এবং চাইল্ড কম্পোজ করার উপযুক্ত আইটেমে স্টেট ও ইভেন্ট পাস করেন। এর ফলে অতিরিক্তভাবে কম্পোজ করা যায় এমন ফাংশন সিগনেচারের ওভারলোড তৈরি হতে পারে।
এমনকি, স্বতন্ত্র ল্যাম্বডা প্যারামিটার হিসেবে ইভেন্ট এক্সপোজ করলে ফাংশন সিগনেচার ওভারলোড হয়ে যেতে পারে, তবে এর ফলে কম্পোজ করা যায় এমন ফাংশনের দায়িত্ব কী তা সর্বাধিক দৃশ্যমান হয়। এটি কী করে তা আপনি এক নজরে দেখতে পাবেন।
এক জায়গায় স্টেট ও ইভেন্ট এনক্যাপসুলেট করার জন্য র্যাপার ক্লাস তৈরি করার চেয়ে প্রপার্টি ড্রিলিং বেশি পছন্দ করা হয়, কারণ এটি কম্পোজ করার উপযুক্ত দায়িত্বের দৃশ্যমানতা কমিয়ে দেয়। র্যাপার ক্লাস না থাকার ফলে, আপনি কম্পোজ করার উপযুক্ত আইটেমকে শুধুমাত্র প্রয়োজনীয় প্যারামিটারই পাস করার সম্ভাবনা বেশি থাকে, যা পেশাদার পদ্ধতি।
এইসব ইভেন্ট নেভিগেশন ইভেন্ট হলেও একই ধরনের সেরা পদ্ধতি প্রযোজ্য হয়, আপনি নেভিগেশন ডকুমেন্ট থেকে সেই বিষয়ে আরও জানতে পারবেন।
আপনি যদি কোনও পারফর্ম্যান্স সংক্রান্ত সমস্যা শনাক্ত করে থাকেন, তাহলে আপনি স্টেট পড়াকে ডিফার করার বিকল্পও বেছে নিতে পারেন। আরও জানতে, আপনি পারফর্ম্যান্স ডকুমেন্ট চেক করতে পারেন।
UI এলিমেন্টের স্টেট
UI এলিমেন্ট স্টেটকে স্ক্রিন লেভেল স্টেট হোল্ডারে হোয়েস্ট করা যেতে পারে, যদি বিজনেস লজিক থাকে যা এটি পড়তে বা লিখতে হবে।
চ্যাট অ্যাপের উদাহরণটি চালিয়ে গেলে, ব্যবহারকারী @ ও একটি ইঙ্গিত টাইপ করলে, অ্যাপটি
গ্রুপ চ্যাটে ব্যবহারকারীর সাজেশন দেখায়। এইসব সাজেশন ডেটা লেয়ার থেকে আসে এবং ব্যবহারকারীর সাজেশন তালিকার গণনা করার লজিককে
বিজনেস লজিক হিসেবে বিবেচনা করা হয়। ফিচারটি দেখতে এইরকম:
@ এবং একটি ইঙ্গিতএই ফিচার প্রয়োগকারী ViewModel-কে নিম্নলিখিতভাবে দেখানো হবে:
class ConversationViewModel(/*...*/) : ViewModel() { // Hoisted state var inputMessage by mutableStateOf("") private set val suggestions: StateFlow<List<Suggestion>> = snapshotFlow { inputMessage } .filter { hasSocialHandleHint(it) } .mapLatest { getHandle(it) } .mapLatest { repository.getSuggestions(it) } .stateIn( scope = viewModelScope, started = SharingStarted.WhileSubscribed(5_000), initialValue = emptyList() ) fun updateInput(newInput: String) { inputMessage = newInput } }
inputMessage হল একটি ভেরিয়েবল যা TextField স্টেট সেভ করে। ব্যবহারকারী
যতবার নতুন ইনপুট টাইপ করেন, অ্যাপটি suggestions তৈরি করার জন্য বিজনেস লজিক কল করে।
suggestions হল স্ক্রিন UI স্টেট এবং StateFlow থেকে
সংগ্রহ করে Compose UI দ্বারা ব্যবহার করা হয়।
Caveat
কিছু Compose UI এলিমেন্ট স্টেটের জন্য, ViewModel-এ হোয়েস্টিংয়ের জন্য
বিশেষ বিবেচনা প্রয়োজন হতে পারে। যেমন, Compose UI এলিমেন্টের কিছু স্টেট হোল্ডার
স্টেট পরিবর্তন করার পদ্ধতি প্রকাশ করে। এর মধ্যে কিছু ফাংশন সাসপেন্ড করা হতে পারে যা
অ্যানিমেশন ট্রিগার করে। এইসব সাসপেন্ড ফাংশন, কম্পোজিশনের
স্কোপে নেই এমন CoroutineScope থেকে কল করলে
এক্সেপশন থ্রো করতে পারে।
ধরা যাক, অ্যাপ ড্রয়ারের কন্টেন্ট ডায়নামিক এবং এটি বন্ধ হয়ে যাওয়ার পরে আপনাকে ডেটা লেয়ার থেকে
এটি ফেচ ও রিফ্রেশ করতে হবে। আপনাকে ড্রয়ারের স্টেটকে ViewModel-এ
হোইস্ট করতে হবে যাতে আপনি স্টেট মালিকের থেকে এই এলিমেন্টে UI ও বিজনেস লজিক
কল করতে পারেন।
তবে, Compose UI থেকে
viewModelScope ব্যবহার করে DrawerState-এর close() মেথড কল করলে, “a
MonotonicFrameClock is not available in this
CoroutineContext”” মেসেজ সহ
IllegalStateException ধরনের রানটাইম ব্যতিক্রম ঘটে।
এটি সমাধান করতে, কম্পোজিশনের জন্য স্কোপ করা CoroutineScope ব্যবহার করুন। এটি CoroutineContext-এ একটি
MonotonicFrameClock প্রদান করে যা
সাসপেন্ড ফাংশনের কাজ করার জন্য প্রয়োজন।
এই ক্র্যাশ ঠিক করতে, ViewModel-এ কোরাউটিনের CoroutineContext-কে কম্পোজিশনের স্কোপে থাকা একটিতে পরিবর্তন করুন। এটি দেখতে এইরকম হতে পারে:
class ConversationViewModel(/*...*/) : ViewModel() { val drawerState = DrawerState(initialValue = DrawerValue.Closed) private val _drawerContent = MutableStateFlow(DrawerContent.Empty) val drawerContent: StateFlow<DrawerContent> = _drawerContent.asStateFlow() fun closeDrawer(uiScope: CoroutineScope) { viewModelScope.launch { withContext(uiScope.coroutineContext) { // Use instead of the default context drawerState.close() } // Fetch drawer content and update state _drawerContent.update { content } } } } // in Compose @Composable private fun ConversationScreen( conversationViewModel: ConversationViewModel = viewModel() ) { val scope = rememberCoroutineScope() ConversationScreen(onCloseDrawer = { conversationViewModel.closeDrawer(uiScope = scope) }) }
আরও জানুন
স্টেট ও Jetpack Compose সম্পর্কে আরও জানতে, নিম্নলিখিত অতিরিক্ত রিসোর্স দেখুন।
নমুনা
কোডল্যাবস
ভিডিও
আপনার জন্য সাজেস্ট করা হয়েছে
- মনে রাখবেন: জাভাস্ক্রিপ্ট বন্ধ থাকলে লিঙ্ক টেক্সট দেখানো হয়
- Compose-এ UI স্টেট সেভ করা
- তালিকা ও গ্রিড
- আপনার Compose UI-এর আর্কিটেকচার তৈরি করা