UI ইভেন্ট

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

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

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

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

UI ইভেন্ট ডিসিশন ট্রি

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

যদি ইভেন্টটি ViewModel থেকে উদ্ভূত হয়, তাহলে UI স্টেট আপডেট করুন। যদি ইভেন্টটি UI থেকে উদ্ভূত হয় এবং বিজনেস লজিকের প্রয়োজন হয়, তাহলে বিজনেস লজিকটি ViewModel-এর কাছে অর্পণ করুন। যদি ইভেন্টটি UI থেকে উদ্ভূত হয় এবং UI বিহেভিয়ার লজিকের প্রয়োজন হয়, তাহলে সরাসরি UI-তেই UI এলিমেন্টের স্টেট পরিবর্তন করুন।
চিত্র ১. ইভেন্ট পরিচালনার জন্য ডিসিশন ট্রি।

ব্যবহারকারীর ইভেন্টগুলি পরিচালনা করুন

যদি ইউজার ইভেন্টগুলো কোনো UI এলিমেন্টের অবস্থা পরিবর্তনের সাথে সম্পর্কিত হয়—উদাহরণস্বরূপ, একটি এক্সপ্যান্ডেবল আইটেমের অবস্থা—তবে UI সরাসরি সেই ইভেন্টগুলো হ্যান্ডেল করতে পারে। যদি ইভেন্টটির জন্য বিজনেস লজিক সম্পাদনের প্রয়োজন হয়, যেমন স্ক্রিনের ডেটা রিফ্রেশ করা, তবে সেটি ViewModel দ্বারা প্রসেস করা উচিত।

নিম্নলিখিত উদাহরণে দেখানো হয়েছে কিভাবে একটি UI এলিমেন্ট প্রসারিত করতে (UI লজিক) এবং স্ক্রিনের ডেটা রিফ্রেশ করতে (বিজনেস লজিক) বিভিন্ন বাটন ব্যবহার করা হয়:

@Composable
fun LatestNewsScreen(viewModel: LatestNewsViewModel = viewModel()) {

    // State of whether more details should be shown
    var expanded by remember { mutableStateOf(false) }

    Column {
        Text("Some text")
        if (expanded) {
            Text("More details")
        }

        Button(
        // The expand details event is processed by the UI that
        // modifies this composable's internal state.
        onClick = { expanded = !expanded }
        ) {
        val expandText = if (expanded) "Collapse" else "Expand"
        Text("$expandText details")
        }

        // The refresh event is processed by the ViewModel that is in charge
        // of the UI's business logic.
        Button(onClick = { viewModel.refreshNews() }) {
            Text("Refresh data")
        }
    }
}

লেজি লিস্টে ব্যবহারকারীর ইভেন্ট

যদি অ্যাকশনটি UI ট্রি-এর আরও নিচের দিকে, যেমন একটি LazyColumn আইটেমে, সংঘটিত হয়, তাহলেও ViewModel এরই ইউজার ইভেন্টগুলো হ্যান্ডেল করা উচিত।

উদাহরণস্বরূপ, ক্লিকযোগ্য আইটেমগুলির একটি তালিকার কথা বিবেচনা করুন। ViewModel ইনস্ট্যান্সটিকে লিস্ট কম্পোজেবল ( MyList )-এর মধ্যে পাস করবেন না, কারণ এটি UI কম্পোনেন্টকে ইমপ্লিমেন্টেশন ডিটেইলসের সাথে দৃঢ়ভাবে আবদ্ধ করে ফেলে।

এর পরিবর্তে, কম্পোজেবল-এর মধ্যে ইভেন্টটিকে একটি ল্যাম্বডা ফাংশন প্যারামিটার হিসেবে প্রকাশ করুন। এর ফলে, কে বা কীভাবে এটি পরিচালনা করছে তা না জেনেই লিস্টটি ইভেন্টটি ট্রিগার করতে পারে।

data class MyItem(val id: Int)

@Composable
fun MyList(
    items: List<String>,
    onItemClick: (MyItem) -> Unit
) {
    Card {
        LazyColumn {
            itemsIndexed(items) { index, string ->
                ListItem(
                    modifier = Modifier.clickable {
                        onItemClick(MyItem(index))
                    },
                    headlineContent = {
                        Text(text = string)
                    }
                )
            }
        }
    }
}

এই পদ্ধতিতে, MyList কম্পোজেবলটি শুধুমাত্র তার প্রদর্শিত ডেটা এবং প্রকাশিত ইভেন্টগুলো নিয়েই কাজ করে। এটির ViewModel-এ কোনো অ্যাক্সেস থাকে না। ইভেন্টটিকে হোইস্ট করে পূর্ববর্তী কোনো কম্পোজেবলের ViewModel-এ পাঠানো হয়।

ইভেন্ট হ্যান্ডলিং সম্পর্কে আরও তথ্যের জন্য, Compose-এর ইভেন্টসমূহ দেখুন।

ব্যবহারকারী ইভেন্ট ফাংশন এবং ইভেন্ট হ্যান্ডলারের নামকরণের নিয়মাবলী

এই নির্দেশিকায়, ব্যবহারকারীর ইভেন্ট পরিচালনা করে এমন ViewModel ফাংশনগুলোর নামকরণ করা হয়েছে তাদের দ্বারা সম্পাদিত কাজের ওপর ভিত্তি করে একটি ক্রিয়াপদ দিয়ে—উদাহরণস্বরূপ: validateInput() বা login()

ডেটার প্রবাহ সুস্পষ্ট করার জন্য কম্পোজ-এর ইভেন্ট হ্যান্ডলারগুলো একটি নির্দিষ্ট নামকরণ পদ্ধতি অনুসরণ করে:

  • প্যারামিটারের নাম: on + Verb + Target (উদাহরণস্বরূপ, onExpandClicked বা onValueChange )।
  • ল্যাম্বডা এক্সপ্রেশন: কম্পোজেবল কল করার সময়, ল্যাম্বডাটি প্রায়শই সেই ইভেন্টের বাস্তবায়ন হয়ে থাকে।

ViewModel ইভেন্টগুলি পরিচালনা করুন

ViewModel থেকে উদ্ভূত UI অ্যাকশন—অর্থাৎ ViewModel ইভেন্ট—এর ফলে সর্বদা UI স্টেট আপডেট হওয়া উচিত। এটি একমুখী ডেটা প্রবাহের (Unidirectional Data Flow ) নীতিমালার সাথে সঙ্গতিপূর্ণ। এটি কনফিগারেশন পরিবর্তনের পরেও ইভেন্টগুলোকে পুনরায় ঘটানো সম্ভব করে এবং নিশ্চিত করে যে UI অ্যাকশনগুলো হারিয়ে যাবে না। ঐচ্ছিকভাবে, আপনি সেভড স্টেট মডিউল (saved state module) ব্যবহার করে প্রসেস বন্ধ হয়ে যাওয়ার পরেও ইভেন্টগুলোকে পুনরায় ঘটানো সম্ভব করতে পারেন।

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

উদাহরণস্বরূপ, একটি লগইন স্ক্রিনের কথা বিবেচনা করুন। আপনি এই স্ক্রিনের UI স্টেটকে নিম্নোক্তভাবে মডেল করতে পারেন:

data class LoginUiState(
    val isLoginInProgress: Boolean = false,
    val errorMessage: String? = null,
    val isUserLoggedIn: Boolean = false
)

লগইন স্ক্রিনটি UI অবস্থার পরিবর্তনের সাথে সাথে প্রতিক্রিয়া দেখায়।

class LoginViewModel : ViewModel() {

    var uiState by mutableStateOf(LoginUiState())

    fun tryLogin(username: String, password: String) {
        viewModelScope.launch {
            // Emit a new state indicating that login is in progress
            uiState = uiState.copy(isLoginInProgress = true)

            uiState = if (login(username, password)) {
                // Emit a new state indicating that login was successful
                uiState.copy(isLoginInProgress = false, isUserLoggedIn = true)
            } else {
                // Emit a new state with the error message
                LoginUiState(isLoginInProgress = false, errorMessage = "Login failed")
            }
        }
    }

    private suspend fun login(username: String, password: String): Boolean {
        delay(1000)
        return (username == "Hello" && password == "World!")
    }
}

@Composable
fun LoginScreen(viewModel: LoginViewModel, onSuccessfulLogin: () -> Unit) {

    val uiState = viewModel.uiState

    LaunchedEffect(uiState) {
        if (uiState.isUserLoggedIn) {
            onSuccessfulLogin()
        }
    }

    if (uiState.isLoginInProgress) {
        CircularProgressIndicator()
    } else {
        LoginForm(
            onLoginAttempt = { username, password ->
                viewModel.tryLogin(username, password)
            },
            errorMessage = uiState.errorMessage
        )
    }
}

ইভেন্ট গ্রহণ করলে অবস্থার আপডেট শুরু হতে পারে।

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

// Models the UI state for the Latest news screen.
data class LatestNewsUiState(
    val news: List<News> = emptyList(),
    val isLoading: Boolean = false,
    val userMessage: String? = null
)

যখন ব্যবসায়িক যুক্তি অনুযায়ী ব্যবহারকারীকে একটি নতুন ক্ষণস্থায়ী বার্তা দেখানোর প্রয়োজন হয়, তখন ViewModel নিম্নলিখিতভাবে UI অবস্থা আপডেট করবে:

class LatestNewsViewModel(/* ... */) : ViewModel() {

    var uiState by mutableStateOf(LatestNewsUiState())
        private set

    fun refreshNews() {
        viewModelScope.launch {
            // If there isn't internet connection, show a new message on the screen.
            if (!internetConnection()) {
                uiState = uiState.copy(userMessage = "No Internet connection")
                return@launch
            }

            // Do something else.
        }
    }

    fun userMessageShown() {
        uiState = uiState.copy(userMessage = null)
    }
}

স্ক্রিনে UI কীভাবে বার্তাটি দেখাচ্ছে তা ViewModel-এর জানার প্রয়োজন নেই; এটি শুধু জানে যে একটি ব্যবহারকারী বার্তা আছে যা দেখানো প্রয়োজন। ক্ষণস্থায়ী বার্তাটি দেখানো হয়ে গেলে, UI-কে ViewModel-কে সে সম্পর্কে অবহিত করতে হয়, যার ফলে আরেকটি UI স্টেট আপডেট userMessage প্রপার্টিটি খালি করে দেয়:

@Composable
fun LatestNewsScreen(
    snackbarHostState: SnackbarHostState,
    viewModel: LatestNewsViewModel = viewModel(),
) {
    // Rest of the UI content.

    // If there are user messages to show on the screen,
    // show it and notify the ViewModel.
    viewModel.uiState.userMessage?.let { userMessage ->
        LaunchedEffect(userMessage) {
            snackbarHostState.showSnackbar(userMessage)
            // Once the message is displayed and dismissed, notify the ViewModel.
            viewModel.userMessageShown()
        }
    }
}

যদিও বার্তাটি ক্ষণস্থায়ী, UI অবস্থাটি প্রতিটি মুহূর্তে স্ক্রিনে যা প্রদর্শিত হয় তার একটি বিশ্বস্ত প্রতিচ্ছবি। ব্যবহারকারীর বার্তাটি হয় প্রদর্শিত হয়, অথবা হয় না।

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

যদি ব্যবহারকারী কোনো বাটনে ট্যাপ করার কারণে UI-তে ইভেন্টটি ট্রিগার হয়, তবে UI কলার কম্পোজেবল-এর কাছে ইভেন্টটি প্রকাশ করার মাধ্যমে সেই কাজটি করে থাকে।

@Composable
fun LoginScreen(
    onHelp: () -> Unit, // Caller navigates to the help screen
    viewModel: LoginViewModel = viewModel()
) {
    // Rest of the UI
    Button(
        onClick = dropUnlessResumed { onHelp() }
    ) {
        Text("Get help")
    }
}

dropUnlessResumed হলো Lifecycle লাইব্রেরির একটি অংশ এবং এটি আপনাকে onHelp ফাংশনটি কেবল তখনই চালাতে দেয় যখন লাইফসাইকেলটি অন্তত RESUMED থাকে।

নেভিগেট করার আগে যদি ডেটা ইনপুটের কোনো বিজনেস লজিক ভ্যালিডেশনের প্রয়োজন হয়, তাহলে ViewModel-কে সেই স্টেটটি UI-এর কাছে প্রকাশ করতে হবে। UI সেই স্টেট পরিবর্তনের সাথে সাড়া দিয়ে সেই অনুযায়ী নেভিগেট করবে। ' Handle ViewModel events' সেকশনটিতে এই ব্যবহারের ক্ষেত্রটি আলোচনা করা হয়েছে। নিচে অনুরূপ একটি কোড দেওয়া হলো:

@Composable
fun LoginScreen(
    onUserLogIn: () -> Unit, // Caller navigates to the right screen
    viewModel: LoginViewModel = viewModel()
) {
    Button(
        onClick = {
            // ViewModel validation is triggered
            viewModel.tryLogin()
        }
    ) {
        Text("Log in")
    }
    // Rest of the UI

    val lifecycle = LocalLifecycleOwner.current.lifecycle
    val currentOnUserLogIn by rememberUpdatedState(onUserLogIn)
    LaunchedEffect(viewModel, lifecycle)  {
        // Whenever the uiState changes, check if the user is logged in and
        // call the `onUserLogin` event when `lifecycle` is at least STARTED
        snapshotFlow { viewModel.uiState }
            .filter { it.isUserLoggedIn }
            .flowWithLifecycle(lifecycle)
            .collect {
                currentOnUserLogIn()
            }
    }
}

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

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

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

class DobValidationViewModel(/* ... */) : ViewModel() {
    var uiState by mutableStateOf(DobValidationUiState())
        private set
}

@Composable
fun DobValidationScreen(
    onNavigateToNextScreen: () -> Unit, // Caller navigates to the right screen
    viewModel: DobValidationViewModel = viewModel()
) {
    // TextField that updates the ViewModel when a date of birth is selected

    var validationInProgress by rememberSaveable { mutableStateOf(false) }

    Button(
        onClick = {
            viewModel.validateInput()
            validationInProgress = true
        }
    ) {
        Text("Continue")
    }
    // Rest of the UI

    /*
        * The following code implements the requirement of advancing automatically
        * to the next screen when a valid date of birth has been introduced
        * and the user wanted to continue with the registration process.
        */

    if (validationInProgress) {
        val lifecycle = LocalLifecycleOwner.current.lifecycle
        val currentNavigateToNextScreen by rememberUpdatedState(onNavigateToNextScreen)
        LaunchedEffect(viewModel, lifecycle) {
            // If the date of birth is valid and the validation is in progress,
            // navigate to the next screen when `lifecycle` is at least STARTED,
            // which is the default Lifecycle.State for the `flowWithLifecycle` operator.
            snapshotFlow { viewModel.uiState }
                .filter { it.isDobValid }
                .flowWithLifecycle(lifecycle)
                .collect {
                    validationInProgress = false
                    currentNavigateToNextScreen()
                }
        }
    }
}

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

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

যদি আপনি মনে করেন যে আপনার UI ইভেন্টের ব্যবহার UI স্টেট আপডেটের মাধ্যমে সমাধান করা যাবে না, তাহলে আপনার অ্যাপে ডেটা প্রবাহ কীভাবে হয় তা পুনর্বিবেচনা করতে হতে পারে। নিম্নলিখিত নীতিগুলি বিবেচনা করুন:

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

নমুনা

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

অতিরিক্ত সম্পদ

UI ইভেন্ট সম্পর্কে আরও তথ্যের জন্য, নিম্নলিখিত অতিরিক্ত রিসোর্সগুলো দেখুন:

কোডল্যাবস

ডকুমেন্টেশন

বিষয়বস্তু দেখুন

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