স্টেট হোল্ডার এবং UI স্টেট

UI লেয়ার গাইডটি UI লেয়ারের জন্য UI স্টেট তৈরি এবং পরিচালনা করার একটি উপায় হিসেবে একমুখী ডেটা প্রবাহ (UDF) নিয়ে আলোচনা করে।

ডেটা ডেটা লেয়ার থেকে UI-এর দিকে একমুখীভাবে প্রবাহিত হয়।
চিত্র ১. একমুখী তথ্য প্রবাহ।

এটি স্টেট হোল্ডার নামক একটি বিশেষ ক্লাসের কাছে UDF ব্যবস্থাপনার দায়িত্ব অর্পণ করার সুবিধাও তুলে ধরে। আপনি একটি ViewModel অথবা একটি সাধারণ ক্লাসের মাধ্যমে স্টেট হোল্ডার প্রয়োগ করতে পারেন। এই ডকুমেন্টটিতে স্টেট হোল্ডার এবং UI লেয়ারে তাদের ভূমিকা নিয়ে বিস্তারিত আলোচনা করা হয়েছে।

এই ডকুমেন্টটি শেষে, UI লেয়ারে অ্যাপ্লিকেশন স্টেট কীভাবে পরিচালনা করতে হয়, অর্থাৎ UI স্টেট প্রোডাকশন পাইপলাইন সম্পর্কে আপনার একটি ধারণা তৈরি হবে। আপনাকে নিম্নলিখিত বিষয়গুলো বুঝতে ও জানতে সক্ষম হতে হবে:

  • UI লেয়ারে বিদ্যমান UI স্টেটের প্রকারভেদগুলো বুঝুন।
  • UI লেয়ারে ঐ UI স্টেটগুলোর উপর যে ধরনের লজিক কাজ করে, তা বুঝুন।
  • ViewModel বা ক্লাসের মতো স্টেট হোল্ডারের উপযুক্ত ইমপ্লিমেন্টেশন কীভাবে বেছে নিতে হয়, তা জানুন।

UI স্টেট প্রোডাকশন পাইপলাইনের উপাদানসমূহ

UI-এর অবস্থা এবং যে লজিক থেকে এটি তৈরি হয়, তা UI লেয়ারকে সংজ্ঞায়িত করে।

UI অবস্থা

UI স্টেট হলো সেই বৈশিষ্ট্য যা UI-কে বর্ণনা করে। UI স্টেট দুই প্রকারের হয়:

  • স্ক্রিন UI স্টেট হলো এমন কিছু যা স্ক্রিনে প্রদর্শন করা প্রয়োজন। উদাহরণস্বরূপ, একটি NewsUiState ক্লাসে UI রেন্ডার করার জন্য প্রয়োজনীয় সংবাদ নিবন্ধ এবং অন্যান্য তথ্য থাকতে পারে। এই স্টেটটি সাধারণত হায়ারার্কির অন্যান্য লেয়ারের সাথে সংযুক্ত থাকে, কারণ এতে অ্যাপের ডেটা থাকে।
  • UI এলিমেন্টের স্টেট বলতে UI এলিমেন্টের সেইসব অন্তর্নিহিত বৈশিষ্ট্যকে বোঝায় যা সেটির রেন্ডারিংকে প্রভাবিত করে। একটি UI এলিমেন্ট দেখানো বা লুকানো যেতে পারে এবং এর একটি নির্দিষ্ট ফন্ট, ফন্ট সাইজ বা ফন্ট কালার থাকতে পারে। Jetpack Compose-এ, স্টেটটি কম্পোজেবলের বাইরে থাকে, এবং আপনি এটিকে কম্পোজেবলের সরাসরি সান্নিধ্য থেকে সরিয়ে কলিং কম্পোজেবল ফাংশন বা কোনো স্টেট হোল্ডারেও নিয়ে যেতে পারেন। এর একটি উদাহরণ হলো Scaffold কম্পোজেবলের জন্য ScaffoldState

যুক্তি

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

লজিক UI স্টেট তৈরি করে
চিত্র ২. UI অবস্থার উৎপাদক হিসেবে লজিক।

একটি অ্যাপ্লিকেশনের লজিক বিজনেস লজিক অথবা UI লজিক হতে পারে:

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

অ্যান্ড্রয়েড লাইফসাইকেল এবং UI স্টেট ও লজিকের প্রকারভেদ

UI লেয়ারের দুটি অংশ রয়েছে: একটি UI লাইফসাইকেলের উপর নির্ভরশীল এবং অন্যটি স্বাধীন। এই বিভাজন প্রতিটি অংশের জন্য উপলব্ধ ডেটা সোর্স নির্ধারণ করে, এবং সেই কারণে বিভিন্ন ধরণের UI স্টেট ও লজিকের প্রয়োজন হয়।

  • UI লাইফসাইকেল ইন্ডিপেন্ডেন্ট : UI লেয়ারের এই অংশটি অ্যাপের ডেটা উৎপাদনকারী লেয়ারগুলো (ডেটা বা ডোমেইন লেয়ার) নিয়ে কাজ করে এবং এটি বিজনেস লজিক দ্বারা সংজ্ঞায়িত হয়। UI-এর লাইফসাইকেল, কনফিগারেশন পরিবর্তন এবং Activity পুনরায় তৈরি হওয়া, UI স্টেট প্রোডাকশন পাইপলাইন সক্রিয় আছে কিনা তা প্রভাবিত করতে পারে, কিন্তু উৎপাদিত ডেটার বৈধতাকে প্রভাবিত করে না।
  • UI লাইফসাইকেল নির্ভর : UI লেয়ারের এই অংশটি UI লজিক নিয়ে কাজ করে এবং এটি লাইফসাইকেল বা কনফিগারেশন পরিবর্তনের দ্বারা সরাসরি প্রভাবিত হয়। এই পরিবর্তনগুলো এর মধ্যে পঠিত ডেটার উৎসগুলোর বৈধতাকে সরাসরি প্রভাবিত করে, এবং ফলস্বরূপ এর অবস্থা কেবল তখনই পরিবর্তিত হতে পারে যখন এর লাইফসাইকেল সক্রিয় থাকে। এর উদাহরণগুলোর মধ্যে রয়েছে রানটাইম পারমিশন এবং স্থানীয় স্ট্রিং-এর মতো কনফিগারেশন নির্ভর রিসোর্স সংগ্রহ করা।

উপরোক্ত বিষয়টিকে নিচের সারণীর মাধ্যমে সংক্ষিপ্ত করা যেতে পারে:

UI জীবনচক্র স্বাধীন UI জীবনচক্র নির্ভর
ব্যবসায়িক যুক্তি UI লজিক
স্ক্রিন UI অবস্থা

UI রাজ্য উৎপাদন পাইপলাইন

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

অর্থাৎ, UI লেয়ার পাইপলাইনের নিম্নলিখিত বিন্যাসগুলো বৈধ:

  • UI স্টেট যা UI নিজেই তৈরি ও পরিচালনা করে। উদাহরণস্বরূপ, একটি সহজ, পুনঃব্যবহারযোগ্য মৌলিক কাউন্টার:

    @Composable
    fun Counter() {
        // The UI state is managed by the UI itself
        var count by remember { mutableStateOf(0) }
        Row {
            Button(onClick = { ++count }) {
                Text(text = "Increment")
            }
            Button(onClick = { --count }) {
                Text(text = "Decrement")
            }
        }
    }
    
  • UI লজিক → UI। উদাহরণস্বরূপ, এমন একটি বাটন দেখানো বা লুকানো যা ব্যবহারকারীকে একটি তালিকার শীর্ষে যেতে সাহায্য করে।

    @Composable
    fun ContactsList(contacts: List<Contact>) {
        val listState = rememberLazyListState()
        val isAtTopOfList by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex < 3
            }
        }
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Show or hide the button (UI logic) based on the list scroll position
        AnimatedVisibility(visible = !isAtTopOfList) {
            ScrollToTopButton()
        }
    }
    
  • ব্যবসায়িক যুক্তি → ইউআই। একটি ইউআই উপাদান যা স্ক্রিনে বর্তমান ব্যবহারকারীর ছবি প্রদর্শন করে।

    @Composable
    fun UserProfileScreen(viewModel: UserProfileViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
    
        // Call on the UserAvatar Composable to display the photo
        UserAvatar(picture = uiState.profilePicture)
    }
    
  • বিজনেস লজিক → ইউআই লজিক → ইউআই। একটি ইউআই এলিমেন্ট যা একটি নির্দিষ্ট ইউআই স্টেটের জন্য স্ক্রিনে সঠিক তথ্য প্রদর্শন করতে স্ক্রল করে।

    @Composable
    fun ContactsList(viewModel: ContactsViewModel = hiltViewModel()) {
        // Read screen UI state from the business logic state holder
        val uiState by viewModel.uiState.collectAsStateWithLifecycle()
        val contacts = uiState.contacts
        val deepLinkedContact = uiState.deepLinkedContact
    
        val listState = rememberLazyListState()
    
        // Create the LazyColumn with the lazyListState
        ...
    
        // Perform UI logic that depends on information from business logic
        if (deepLinkedContact != null && contacts.isNotEmpty()) {
            LaunchedEffect(listState, deepLinkedContact, contacts) {
                val deepLinkedContactIndex = contacts.indexOf(deepLinkedContact)
                if (deepLinkedContactIndex >= 0) {
                  // Scroll to deep linked item
                  listState.animateScrollToItem(deepLinkedContactIndex)
                }
            }
        }
    }
    

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

ডেটা উৎপাদনকারী স্তর থেকে UI-তে ডেটা প্রবাহিত হয়।
চিত্র ৩. UI স্তরে লজিকের প্রয়োগ।

রাষ্ট্রধারীগণ এবং তাদের দায়িত্বসমূহ

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

এর ফলে নিম্নলিখিত সুবিধাগুলো পাওয়া যায়:

  • সরল UI : UI কেবল তার অবস্থাকে আবদ্ধ করে।
  • রক্ষণাবেক্ষণযোগ্যতা : স্টেট হোল্ডারে সংজ্ঞায়িত লজিকটি UI পরিবর্তন না করেই পুনরাবৃত্তি করা যেতে পারে।
  • পরীক্ষাযোগ্যতা : UI এবং এর স্টেট প্রোডাকশন লজিক স্বাধীনভাবে পরীক্ষা করা যেতে পারে।
  • পাঠযোগ্যতা : কোডের পাঠকরা UI প্রেজেন্টেশন কোড এবং UI স্টেট প্রোডাকশন কোডের মধ্যে পার্থক্য স্পষ্টভাবে দেখতে পারেন।

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

রাষ্ট্রীয় ধারকদের প্রকারভেদ

UI স্টেট এবং লজিকের প্রকারভেদের মতোই, UI লেয়ারে দুই ধরনের স্টেট হোল্ডার রয়েছে, যা UI লাইফসাইকেলের সাথে তাদের সম্পর্কের ভিত্তিতে সংজ্ঞায়িত হয়:

  • ব্যবসায়িক যুক্তির অবস্থা ধারক।
  • UI লজিক স্টেট হোল্ডার।

পরবর্তী বিভাগগুলিতে বিভিন্ন ধরণের স্টেট হোল্ডারদের সম্পর্কে আরও বিশদভাবে আলোচনা করা হয়েছে, যার শুরুটা হয়েছে বিজনেস লজিক স্টেট হোল্ডারকে দিয়ে।

ব্যবসায়িক যুক্তি এবং এর অবস্থা ধারক

বিজনেস লজিক স্টেট হোল্ডাররা ইউজার ইভেন্টগুলো প্রসেস করে এবং ডেটা বা ডোমেইন লেয়ার থেকে ডেটাকে স্ক্রিন UI স্টেটে রূপান্তর করে। অ্যান্ড্রয়েড লাইফসাইকেল এবং অ্যাপ কনফিগারেশনের পরিবর্তনগুলো বিবেচনা করে সর্বোত্তম ইউজার এক্সপেরিয়েন্স প্রদানের জন্য, বিজনেস লজিক ব্যবহারকারী স্টেট হোল্ডারদের নিম্নলিখিত বৈশিষ্ট্যগুলো থাকা উচিত:

সম্পত্তি বিস্তারিত
UI রাজ্য তৈরি করে বিজনেস লজিক স্টেট হোল্ডাররা তাদের UI-এর জন্য UI স্টেট তৈরি করার দায়িত্বে থাকেন। এই UI স্টেট প্রায়শই ইউজার ইভেন্ট প্রসেস করা এবং ডোমেইন ও ডেটা লেয়ার থেকে ডেটা রিড করার ফলস্বরূপ তৈরি হয়।
কার্যকলাপ বিনোদনের মাধ্যমে ধরে রাখা বিজনেস লজিক স্টেট হোল্ডাররা Activity পুনরায় তৈরি হওয়ার পরেও তাদের স্টেট এবং স্টেট প্রসেসিং পাইপলাইন ধরে রাখে, যা একটি নির্বিঘ্ন ব্যবহারকারীর অভিজ্ঞতা প্রদানে সহায়তা করে। যেসব ক্ষেত্রে স্টেট হোল্ডারকে ধরে রাখা সম্ভব হয় না এবং এটি পুনরায় তৈরি করা হয় (সাধারণত প্রসেস বন্ধ হয়ে যাওয়ার পরে), সেক্ষেত্রে একটি সামঞ্জস্যপূর্ণ ব্যবহারকারীর অভিজ্ঞতা নিশ্চিত করার জন্য স্টেট হোল্ডারটির অবশ্যই তার সর্বশেষ স্টেটটি সহজে পুনরায় তৈরি করার সক্ষমতা থাকতে হবে।
দীর্ঘজীবী অবস্থা ধারণ করা নেভিগেশন গন্তব্যস্থলের স্টেট ব্যবস্থাপনার জন্য প্রায়শই বিজনেস লজিক স্টেট হোল্ডার ব্যবহার করা হয়। ফলে, নেভিগেশন গ্রাফ থেকে অপসারণ না করা পর্যন্ত এগুলি নেভিগেশন পরিবর্তনের পরেও নিজেদের স্টেট বজায় রাখে।
এর UI অনন্য এবং এটি পুনঃব্যবহারযোগ্য নয়। বিজনেস লজিক স্টেট হোল্ডারগুলো সাধারণত কোনো নির্দিষ্ট অ্যাপ ফাংশনের জন্য স্টেট তৈরি করে, যেমন TaskEditViewModel বা TaskListViewModel , এবং তাই এটি শুধুমাত্র সেই অ্যাপ ফাংশনটির জন্যই প্রযোজ্য হয়। একই স্টেট হোল্ডার বিভিন্ন ফর্ম ফ্যাক্টরে এই অ্যাপ ফাংশনগুলোকে সমর্থন করতে পারে। উদাহরণস্বরূপ, অ্যাপটির মোবাইল, টিভি এবং ট্যাবলেট সংস্করণগুলো একই বিজনেস লজিক স্টেট হোল্ডার পুনরায় ব্যবহার করতে পারে।

উদাহরণস্বরূপ, "Now in Android " অ্যাপের লেখক নেভিগেশন গন্তব্যটি বিবেচনা করুন:

"Now in Android" অ্যাপটি দেখায় যে, অ্যাপের কোনো প্রধান ফাংশনের প্রতিনিধিত্বকারী একটি নেভিগেশন গন্তব্যের নিজস্ব স্বতন্ত্র বিজনেস লজিক স্টেট হোল্ডার থাকা উচিত।
চিত্র ৪. অ্যান্ড্রয়েড অ্যাপে ‘Now’।

এই ক্ষেত্রে, বিজনেস লজিক স্টেট হোল্ডার হিসেবে AuthorViewModel UI স্টেট তৈরি করে:

@HiltViewModel
class AuthorViewModel @Inject constructor(
    savedStateHandle: SavedStateHandle,
    private val authorsRepository: AuthorsRepository,
    newsRepository: NewsRepository
) : ViewModel() {

    val uiState: StateFlow<AuthorScreenUiState> = 

    // Business logic
    fun followAuthor(followed: Boolean) {
      
    }
}

লক্ষ্য করুন যে AuthorViewModel পূর্বে বর্ণিত অ্যাট্রিবিউটগুলো রয়েছে:

সম্পত্তি বিস্তারিত
AuthorScreenUiState তৈরি করে AuthorViewModel AuthorsRepository এবং NewsRepository থেকে ডেটা পড়ে এবং সেই ডেটা ব্যবহার করে AuthorScreenUiState তৈরি করে। এছাড়াও, যখন ব্যবহারকারী কোনো Author ফলো বা আনফলো করতে চান, তখন এটি AuthorsRepository ওপর দায়িত্ব অর্পণ করে বিজনেস লজিক প্রয়োগ করে।
ডেটা লেয়ারে প্রবেশাধিকার আছে এর কনস্ট্রাক্টরে AuthorsRepository এবং NewsRepository এর একটি করে ইনস্ট্যান্স পাস করা হয়, যা এটিকে একজন Author অনুসরণ করার বিজনেস লজিক বাস্তবায়ন করতে সক্ষম করে।
বেঁচে থাকা Activity বিনোদন যেহেতু এটি একটি ViewModel দিয়ে বাস্তবায়িত হয়েছে, তাই দ্রুত Activity পুনরায় তৈরি করলেও এটি সংরক্ষিত থাকবে। প্রসেস বন্ধ হয়ে গেলে, ডেটা লেয়ার থেকে UI স্টেট পুনরুদ্ধার করার জন্য প্রয়োজনীয় ন্যূনতম তথ্য পেতে SavedStateHandle অবজেক্টটি পড়া যেতে পারে।
দীর্ঘজীবী অবস্থা ধারণ করে ViewModel টি ন্যাভিগেশন গ্রাফের স্কোপের মধ্যে থাকে, তাই যতক্ষণ না অথর ডেস্টিনেশনটি ন্যাভ গ্রাফ থেকে সরানো হচ্ছে, uiState StateFlow এর মধ্যে থাকা UI স্টেট মেমরিতে থেকে যায়। StateFlow ব্যবহারের আরেকটি সুবিধা হলো, এটি স্টেট তৈরি করার বিজনেস লজিকের প্রয়োগকে লেজি (lazy) করে তোলে, কারণ শুধুমাত্র তখনই স্টেট তৈরি হয় যখন UI স্টেটের কোনো কালেক্টর থাকে।
এর UI-এর জন্য এটি অনন্য। AuthorViewModel শুধুমাত্র অথর নেভিগেশন ডেস্টিনেশনের জন্য প্রযোজ্য এবং অন্য কোথাও এটি পুনরায় ব্যবহার করা যাবে না। যদি বিভিন্ন নেভিগেশন ডেস্টিনেশনে কোনো বিজনেস লজিক পুনরায় ব্যবহার করা হয়, তবে সেই বিজনেস লজিকটিকে অবশ্যই একটি ডেটা- অথবা ডোমেইন-লেয়ার-স্কোপড কম্পোনেন্টের মধ্যে আবদ্ধ করতে হবে।

ব্যবসায়িক যুক্তির অবস্থা ধারক হিসেবে ভিউমডেল

অ্যান্ড্রয়েড ডেভেলপমেন্টে ভিউমডেলের সুবিধাগুলো এটিকে বিজনেস লজিকে অ্যাক্সেস প্রদান এবং স্ক্রিনে প্রদর্শনের জন্য অ্যাপ্লিকেশন ডেটা প্রস্তুত করার ক্ষেত্রে উপযুক্ত করে তোলে। এই সুবিধাগুলোর মধ্যে নিম্নলিখিতগুলো অন্তর্ভুক্ত:

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

UI লজিক এবং এর স্টেট হোল্ডার

UI লজিক হলো এমন লজিক যা UI নিজেই সরবরাহ করা ডেটার উপর কাজ করে। এটি UI এলিমেন্টের স্টেটের উপর, অথবা পারমিশন এপিআই বা Resources মতো UI ডেটা সোর্সের উপর হতে পারে। যে স্টেট হোল্ডাররা UI লজিক ব্যবহার করে, তাদের সাধারণত নিম্নলিখিত বৈশিষ্ট্যগুলো থাকে:

  • UI স্টেট তৈরি করে এবং UI এলিমেন্টগুলোর স্টেট পরিচালনা করে
  • Activity পুনঃসৃষ্টির পর টিকে থাকে না : UI লজিকে থাকা স্টেট হোল্ডাররা প্রায়শই UI-এর নিজস্ব ডেটা সোর্সের উপর নির্ভরশীল থাকে, এবং কনফিগারেশন পরিবর্তনের পরেও এই তথ্য ধরে রাখার চেষ্টা করলে প্রায়শই মেমোরি লিক হয়। যদি স্টেট হোল্ডারদের কনফিগারেশন পরিবর্তনের পরেও ডেটা ধরে রাখার প্রয়োজন হয়, তবে তাদের এমন একটি কম্পোনেন্টের কাছে দায়িত্ব অর্পণ করা উচিত যা Activity পুনঃসৃষ্টির পরেও টিকে থাকার জন্য আরও উপযুক্ত। উদাহরণস্বরূপ, Jetpack Compose-এ, remembered ফাংশন দিয়ে তৈরি Composable UI এলিমেন্টের স্টেটগুলো প্রায়শই Activity পুনঃসৃষ্টির পরেও স্টেট সংরক্ষণ করার জন্য rememberSaveable এর কাছে দায়িত্ব অর্পণ করে। এই ধরনের ফাংশনের উদাহরণ হলো rememberScaffoldState() এবং rememberLazyListState()
  • UI-স্কোপড ডেটা সোর্সের রেফারেন্স রয়েছে : লাইফসাইকেল API এবং রিসোর্সের মতো ডেটা সোর্সগুলোকে নিরাপদে রেফারেন্স করা ও পড়া যায়, কারণ UI লজিক স্টেট হোল্ডারের লাইফসাইকেল UI-এর মতোই।
  • একাধিক UI-তে পুনঃব্যবহারযোগ্য : একই UI লজিক স্টেট হোল্ডারের বিভিন্ন ইনস্ট্যান্স অ্যাপের বিভিন্ন অংশে পুনঃব্যবহার করা যেতে পারে। উদাহরণস্বরূপ, একটি চিপ গ্রুপের জন্য ব্যবহারকারীর ইনপুট ইভেন্টগুলি পরিচালনা করার একটি স্টেট হোল্ডার সার্চ পেজে চিপ ফিল্টার করার জন্য এবং ইমেইলের প্রাপকদের জন্য "টু" ফিল্ডেও ব্যবহার করা যেতে পারে।

UI লজিক স্টেট হোল্ডার সাধারণত একটি সাধারণ ক্লাস দিয়ে ইমপ্লিমেন্ট করা হয়। এর কারণ হলো, UI লজিক স্টেট হোল্ডার তৈরির দায়িত্ব UI নিজেই পালন করে এবং UI লজিক স্টেট হোল্ডারের লাইফসাইকেলও UI-এর মতোই। উদাহরণস্বরূপ, Jetpack Compose-এ স্টেট হোল্ডারটি Composition-এর একটি অংশ এবং এটি Composition-এর লাইফসাইকেল অনুসরণ করে।

পূর্ববর্তী বিষয়টি Now in Android স্যাম্পলের নিম্নলিখিত উদাহরণে ব্যাখ্যা করা যেতে পারে:

এখন অ্যান্ড্রয়েডে UI লজিক পরিচালনা করার জন্য একটি সাধারণ ক্লাস স্টেট হোল্ডার ব্যবহার করা হয়।
চিত্র ৫. অ্যান্ড্রয়েড নমুনা অ্যাপ ‘Now’।

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

যেহেতু NiaApp কম্পোজেবল ফাংশনে ব্যবহৃত উপযুক্ত নেভিগেশন UI এলিমেন্ট নির্ধারণের লজিকটি বিজনেস লজিকের উপর নির্ভর করে না, তাই এটিকে NiaAppState নামক একটি সাধারণ ক্লাস স্টেট হোল্ডার দ্বারা পরিচালনা করা যেতে পারে:

@Stable
class NiaAppState(
    val navController: NavHostController,
    val windowSizeClass: WindowSizeClass
) {

    // UI logic
    val shouldShowBottomBar: Boolean
        get() = windowSizeClass.widthSizeClass == WindowWidthSizeClass.Compact ||
            windowSizeClass.heightSizeClass == WindowHeightSizeClass.Compact

    // UI logic
    val shouldShowNavRail: Boolean
        get() = !shouldShowBottomBar

   // UI State
    val currentDestination: NavDestination?
        @Composable get() = navController
            .currentBackStackEntryAsState().value?.destination

    // UI logic
    fun navigate(destination: NiaNavigationDestination, route: String? = null) { /* ... */ }

     /* ... */
}

পূর্ববর্তী উদাহরণে, NiaAppState সম্পর্কিত নিম্নলিখিত বিবরণগুলি উল্লেখযোগ্য:

  • Activity পুনঃসৃষ্টির পর টিকে থাকে না : কম্পোজ নামকরণের নিয়ম অনুসরণ করে rememberNiaAppState নামক একটি কম্পোজেবল ফাংশন দিয়ে তৈরি করার মাধ্যমে কম্পোজিশনে NiaAppState remembered হয়। Activity পুনঃসৃষ্টির পর, পূর্ববর্তী ইনস্ট্যান্সটি হারিয়ে যায় এবং পুনঃসৃষ্ট Activity নতুন কনফিগারেশনের জন্য উপযুক্ত, এর সমস্ত ডিপেন্ডেন্সি সহ একটি নতুন ইনস্ট্যান্স তৈরি হয়। এই ডিপেন্ডেন্সিগুলো নতুন হতে পারে অথবা পূর্ববর্তী কনফিগারেশন থেকে পুনরুদ্ধার করা হতে পারে। উদাহরণস্বরূপ, rememberNavController() NiaAppState কনস্ট্রাক্টরে ব্যবহৃত হয় এবং Activity পুনঃসৃষ্টির পরেও স্টেট সংরক্ষণ করার জন্য এটি rememberSaveable এর উপর দায়িত্ব অর্পণ করে।
  • UI-স্কোপড ডেটা সোর্সের রেফারেন্স রয়েছে : navigationController , Resources এবং অন্যান্য অনুরূপ লাইফসাইকেল-স্কোপড টাইপের রেফারেন্স NiaAppState এ নিরাপদে রাখা যেতে পারে, কারণ তাদের লাইফসাইকেল স্কোপ একই।

স্টেট হোল্ডারের জন্য ViewModel এবং সাধারণ ক্লাসের মধ্যে একটি বেছে নিন।

পূর্ববর্তী বিভাগগুলো থেকে, একটি ViewModel এবং একটি সাধারণ ক্লাস স্টেট হোল্ডারের মধ্যে নির্বাচন করা মূলত UI স্টেটে প্রয়োগ করা লজিক এবং সেই লজিকটি যে ডেটার উৎসের উপর কাজ করে, তার উপর নির্ভর করে।

সংক্ষেপে, নিম্নলিখিত ডায়াগ্রামটি UI স্টেট উৎপাদন পাইপলাইনে স্টেট হোল্ডারদের অবস্থান দেখায়:

ডেটা ডেটা উৎপাদনকারী স্তর থেকে UI স্তরে প্রবাহিত হয়।
চিত্র ৬। UI স্টেট প্রোডাকশন পাইপলাইনে স্টেট হোল্ডারগণ। তীরচিহ্নগুলো ডেটা প্রবাহ নির্দেশ করে।

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

রাষ্ট্রীয় ধারকদের সাথে আপস করা যেতে পারে।

অবস্থাধারীরা অন্য অবস্থাধারীদের উপর নির্ভর করতে পারে, যতক্ষণ পর্যন্ত সেই নির্ভরতাগুলোর জীবনকাল সমান বা তার চেয়ে কম হয়। এর উদাহরণগুলো হলো:

  • একটি UI লজিক স্টেট হোল্ডার অন্য একটি UI লজিক স্টেট হোল্ডারের উপর নির্ভর করতে পারে।
  • একটি স্ক্রিন লেভেল স্টেট হোল্ডার একটি UI লজিক স্টেট হোল্ডারের উপর নির্ভর করতে পারে।

নিম্নলিখিত কোড স্নিপেটটি দেখায় যে কীভাবে Compose-এর DrawerState অন্য একটি অভ্যন্তরীণ স্টেট হোল্ডার, SwipeableState উপর নির্ভর করে, এবং কীভাবে একটি অ্যাপের UI লজিক স্টেট হোল্ডার DrawerState উপর নির্ভর করতে পারে:

@Stable
class DrawerState(/* ... */) {
  internal val swipeableState = SwipeableState(/* ... */)
  // ...
}

@Stable
class MyAppState(
  private val drawerState: DrawerState,
  private val navController: NavHostController
) { /* ... */ }

@Composable
fun rememberMyAppState(
  drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
  navController: NavHostController = rememberNavController()
): MyAppState = remember(drawerState, navController) {
  MyAppState(drawerState, navController)
}

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

যদি স্বল্পস্থায়ী স্টেট হোল্ডারের কোনো উচ্চতর স্কোপের স্টেট হোল্ডার থেকে নির্দিষ্ট তথ্যের প্রয়োজন হয়, তবে স্টেট হোল্ডার ইনস্ট্যান্সটি পাস না করে শুধুমাত্র প্রয়োজনীয় তথ্যটুকু প্যারামিটার হিসেবে পাস করুন। উদাহরণস্বরূপ, নিম্নলিখিত কোড স্নিপেটে, UI লজিক স্টেট হোল্ডার ক্লাসটি সম্পূর্ণ ViewModel ইনস্ট্যান্সকে ডিপেন্ডেন্সি হিসেবে পাস না করে, ViewModel থেকে শুধুমাত্র তার প্রয়োজনীয় তথ্যগুলোই প্যারামিটার হিসেবে গ্রহণ করে।

class MyScreenViewModel(/* ... */) {
  val uiState: StateFlow<MyScreenUiState> = /* ... */
  fun doSomething() { /* ... */ }
  fun doAnotherThing() { /* ... */ }
  // ...
}

@Stable
class MyScreenState(
  // DO NOT pass a ViewModel instance to a plain state holder class
  // private val viewModel: MyScreenViewModel,

  // Instead, pass only what it needs as a dependency
  private val someState: StateFlow<SomeState>,
  private val doSomething: () -> Unit,

  // Other UI-scoped types
  private val scaffoldState: ScaffoldState
) {
  /* ... */
}

@Composable
fun rememberMyScreenState(
  someState: StateFlow<SomeState>,
  doSomething: () -> Unit,
  scaffoldState: ScaffoldState = rememberScaffoldState()
): MyScreenState = remember(someState, doSomething, scaffoldState) {
  MyScreenState(someState, doSomething, scaffoldState)
}

@Composable
fun MyScreen(
  modifier: Modifier = Modifier,
  viewModel: MyScreenViewModel = viewModel(),
  state: MyScreenState = rememberMyScreenState(
    someState = viewModel.uiState.map { it.toSomeState() },
    doSomething = viewModel::doSomething
  ),
  // ...
) {
  /* ... */
}

নিম্নলিখিত ডায়াগ্রামটি পূর্ববর্তী কোড স্নিপেটের UI এবং বিভিন্ন স্টেট হোল্ডারদের মধ্যকার নির্ভরশীলতা উপস্থাপন করে:

UI লজিক স্টেট হোল্ডার এবং স্ক্রিন লেভেল স্টেট হোল্ডার উভয়ের উপর নির্ভর করে UI।
চিত্র ৭। বিভিন্ন অবস্থা ধারকের উপর নির্ভরশীল UI। তীরচিহ্নগুলো নির্ভরশীলতা বোঝায়।

নমুনা

নিম্নলিখিত গুগল নমুনাগুলি UI স্তরে স্টেট হোল্ডারদের ব্যবহার প্রদর্শন করে। এই নির্দেশনাটি বাস্তবে দেখতে এগুলি ঘুরে দেখুন:

{% হুবহু %} {% endverbatim %} {% হুবহু %} {% endverbatim %}