UI ইভেন্টগুলি এমন ক্রিয়া যা UI স্তরে পরিচালনা করা উচিত, হয় UI দ্বারা বা ViewModel দ্বারা৷ সবচেয়ে সাধারণ ধরনের ইভেন্ট হল ব্যবহারকারী ইভেন্ট । ব্যবহারকারী অ্যাপের সাথে ইন্টারঅ্যাক্ট করে ব্যবহারকারীর ইভেন্ট তৈরি করে—উদাহরণস্বরূপ, স্ক্রীনে ট্যাপ করে বা অঙ্গভঙ্গি তৈরি করে। UI তারপর কলব্যাক যেমন onClick()
শ্রোতাদের ব্যবহার করে এই ইভেন্টগুলি ব্যবহার করে।
ViewModel সাধারণত একটি নির্দিষ্ট ব্যবহারকারী ইভেন্টের ব্যবসায়িক যুক্তি পরিচালনার জন্য দায়ী—উদাহরণস্বরূপ, ব্যবহারকারী কিছু ডেটা রিফ্রেশ করতে একটি বোতামে ক্লিক করে। সাধারণত, ViewModel UI কল করতে পারে এমন ফাংশনগুলিকে প্রকাশ করে এটি পরিচালনা করে। ব্যবহারকারীর ইভেন্টগুলিতে UI আচরণের যুক্তিও থাকতে পারে যা UI সরাসরি পরিচালনা করতে পারে—উদাহরণস্বরূপ, একটি ভিন্ন স্ক্রিনে নেভিগেট করা বা একটি Snackbar
দেখানো।
যদিও বিভিন্ন মোবাইল প্ল্যাটফর্ম বা ফর্ম ফ্যাক্টরগুলিতে একই অ্যাপের জন্য ব্যবসায়িক যুক্তি একই থাকে, UI আচরণের যুক্তি হল একটি বাস্তবায়নের বিবরণ যা এই ক্ষেত্রেগুলির মধ্যে আলাদা হতে পারে। UI স্তর পৃষ্ঠা এই ধরনের যুক্তিকে নিম্নরূপ সংজ্ঞায়িত করে:
- ব্যবসায়িক লজিক রাজ্যের পরিবর্তনগুলির সাথে কী করতে হবে তা বোঝায়—উদাহরণস্বরূপ, একটি অর্থপ্রদান করা বা ব্যবহারকারীর পছন্দগুলি সংরক্ষণ করা৷ ডোমেন এবং ডেটা স্তরগুলি সাধারণত এই যুক্তিটি পরিচালনা করে। এই নির্দেশিকা জুড়ে, আর্কিটেকচার কম্পোনেন্টস ভিউমডেল ক্লাসটি ব্যবসায়িক যুক্তি পরিচালনা করে এমন ক্লাসগুলির জন্য একটি মতামতযুক্ত সমাধান হিসাবে ব্যবহৃত হয়।
- UI আচরণের যুক্তি বা UI লজিক বলতে বোঝায় কীভাবে রাজ্যের পরিবর্তনগুলি প্রদর্শন করতে হয় —উদাহরণস্বরূপ, নেভিগেশন লজিক বা কীভাবে ব্যবহারকারীকে বার্তাগুলি দেখাতে হয়। UI এই যুক্তিটি পরিচালনা করে।
UI ইভেন্ট ডিসিশন ট্রি
নিম্নলিখিত চিত্রটি একটি নির্দিষ্ট ইভেন্ট ব্যবহারের ক্ষেত্রে পরিচালনা করার জন্য সর্বোত্তম পদ্ধতির সন্ধান করার জন্য একটি সিদ্ধান্ত গাছ দেখায়। এই গাইডের বাকি অংশ এই পন্থাগুলি বিস্তারিতভাবে ব্যাখ্যা করে।
![যদি ইভেন্টটি ViewModel থেকে উদ্ভূত হয়, তাহলে UI অবস্থা আপডেট করুন। যদি ইভেন্টটি UI-তে উদ্ভূত হয় এবং ব্যবসায়িক যুক্তির প্রয়োজন হয়, তাহলে ViewModel-এ ব্যবসার যুক্তি অর্পণ করুন। যদি ইভেন্টটি UI থেকে উদ্ভূত হয় এবং UI আচরণের যুক্তির প্রয়োজন হয়, তাহলে UI এলিমেন্টের অবস্থা সরাসরি UI-তে পরিবর্তন করুন।](https://developer.android.com/static/topic/libraries/architecture/images/mad-arch-uievents-tree.png?authuser=0&hl=bn)
ব্যবহারকারীর ইভেন্টগুলি পরিচালনা করুন
UI ব্যবহারকারীর ইভেন্টগুলি সরাসরি পরিচালনা করতে পারে যদি সেই ইভেন্টগুলি একটি UI উপাদানের অবস্থার পরিবর্তনের সাথে সম্পর্কিত হয় - উদাহরণস্বরূপ, একটি প্রসারণযোগ্য আইটেমের অবস্থা৷ যদি ইভেন্টের জন্য ব্যবসায়িক যুক্তির প্রয়োজন হয়, যেমন স্ক্রিনে ডেটা রিফ্রেশ করা, এটি ভিউমডেল দ্বারা প্রক্রিয়া করা উচিত।
নিম্নলিখিত উদাহরণটি দেখায় যে কীভাবে একটি UI উপাদান (UI লজিক) প্রসারিত করতে এবং স্ক্রিনে ডেটা রিফ্রেশ করতে (ব্যবসায়িক যুক্তি) বিভিন্ন বোতাম ব্যবহার করা হয়:
ভিউ
class LatestNewsActivity : AppCompatActivity() {
private lateinit var binding: ActivityLatestNewsBinding
private val viewModel: LatestNewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
// The expand details event is processed by the UI that
// modifies a View's internal state.
binding.expandButton.setOnClickListener {
binding.expandedSection.visibility = View.VISIBLE
}
// The refresh event is processed by the ViewModel that is in charge
// of the business logic.
binding.refreshButton.setOnClickListener {
viewModel.refreshNews()
}
}
}
রচনা করুন
@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")
}
}
}
RecyclerViews ব্যবহারকারী ইভেন্ট
যদি ক্রিয়াটি UI ট্রির আরও নীচে উত্পাদিত হয়, যেমন একটি RecyclerView
আইটেম বা একটি কাস্টম View
, ViewModel
এখনও ব্যবহারকারীর ইভেন্টগুলি পরিচালনা করে এমন একটি হওয়া উচিত৷
উদাহরণস্বরূপ, ধরুন যে NewsActivity
এর সমস্ত সংবাদ আইটেমে একটি বুকমার্ক বোতাম রয়েছে৷ ViewModel
বুকমার্ক করা সংবাদ আইটেমের আইডি জানতে হবে। যখন ব্যবহারকারী একটি নিউজ আইটেম বুকমার্ক করে, তখন RecyclerView
অ্যাডাপ্টার ViewModel
থেকে প্রকাশিত addBookmark(newsId)
ফাংশনকে কল করে না, যার জন্য ViewModel
উপর নির্ভরতা প্রয়োজন। পরিবর্তে, ViewModel
NewsItemUiState
নামক একটি রাষ্ট্রীয় বস্তুকে প্রকাশ করে যা ইভেন্ট পরিচালনার জন্য বাস্তবায়ন ধারণ করে:
data class NewsItemUiState(
val title: String,
val body: String,
val bookmarked: Boolean = false,
val publicationDate: String,
val onBookmark: () -> Unit
)
class LatestNewsViewModel(
private val formatDateUseCase: FormatDateUseCase,
private val repository: NewsRepository
)
val newsListUiItems = repository.latestNews.map { news ->
NewsItemUiState(
title = news.title,
body = news.body,
bookmarked = news.bookmarked,
publicationDate = formatDateUseCase(news.publicationDate),
// Business logic is passed as a lambda function that the
// UI calls on click events.
onBookmark = {
repository.addBookmark(news.id)
}
)
}
}
এইভাবে, RecyclerView
অ্যাডাপ্টার শুধুমাত্র প্রয়োজনীয় ডেটার সাথে কাজ করে: NewsItemUiState
অবজেক্টের তালিকা। অ্যাডাপ্টারের সম্পূর্ণ ViewModel-এ অ্যাক্সেস নেই, এটি ViewModel দ্বারা উন্মুক্ত কার্যকারিতা অপব্যবহারের সম্ভাবনা কম করে তোলে। আপনি যখন শুধুমাত্র কার্যকলাপ শ্রেণীকে ViewModel-এর সাথে কাজ করার অনুমতি দেন, তখন আপনি আলাদা দায়িত্ব নেন। এটি নিশ্চিত করে যে ভিউ বা RecyclerView
অ্যাডাপ্টারের মতো UI-নির্দিষ্ট বস্তুগুলি ভিউমডেলের সাথে সরাসরি ইন্টারঅ্যাক্ট করে না।
ব্যবহারকারী ইভেন্ট ফাংশন জন্য নামকরণ নিয়মাবলী
এই নির্দেশিকায়, ভিউমডেল ফাংশনগুলি যেগুলি ব্যবহারকারীর ইভেন্টগুলি পরিচালনা করে সেগুলিকে একটি ক্রিয়া দিয়ে নামকরণ করা হয়েছে যা তারা পরিচালনা করে—উদাহরণস্বরূপ: addBookmark(id)
বা logIn(username, password)
৷
ViewModel ইভেন্টগুলি পরিচালনা করুন
ভিউমডেল—ভিউমডেল ইভেন্টগুলি থেকে উদ্ভূত UI অ্যাকশনগুলি সর্বদা একটি UI স্টেট আপডেটের ফলাফল হওয়া উচিত। এটি ইউনিডাইরেকশনাল ডেটা ফ্লো -এর নীতির সাথে সঙ্গতিপূর্ণ। এটি কনফিগারেশন পরিবর্তনের পরে ইভেন্টগুলিকে পুনরুত্পাদনযোগ্য করে তোলে এবং গ্যারান্টি দেয় যে UI অ্যাকশনগুলি হারিয়ে যাবে না। ঐচ্ছিকভাবে, আপনি যদি সংরক্ষিত স্টেট মডিউলটি ব্যবহার করেন তবে প্রক্রিয়া মৃত্যুর পরেও আপনি ঘটনাগুলিকে পুনরুত্পাদনযোগ্য করতে পারেন৷
UI স্টেটে UI অ্যাকশন ম্যাপ করা সবসময় একটি সহজ প্রক্রিয়া নয়, তবে এটি সহজ যুক্তির দিকে নিয়ে যায়। উদাহরণ স্বরূপ, UI কে একটি নির্দিষ্ট স্ক্রিনে কীভাবে নেভিগেট করা যায় তা নির্ধারণ করে আপনার চিন্তার প্রক্রিয়া শেষ হওয়া উচিত নয়। আপনাকে আরও চিন্তা করতে হবে এবং কীভাবে আপনার UI রাজ্যে সেই ব্যবহারকারীর প্রবাহকে উপস্থাপন করতে হবে তা বিবেচনা করতে হবে। অন্য কথায়: ইউআই-এর কী কী কাজ করতে হবে তা নিয়ে ভাববেন না; সেই কর্মগুলি কীভাবে UI অবস্থাকে প্রভাবিত করে সে সম্পর্কে চিন্তা করুন।
উদাহরণস্বরূপ, যখন ব্যবহারকারী লগইন স্ক্রিনে লগ ইন করেন তখন হোম স্ক্রিনে নেভিগেট করার ক্ষেত্রে বিবেচনা করুন৷ আপনি UI অবস্থায় এটিকে নিম্নরূপ মডেল করতে পারেন:
data class LoginUiState(
val isLoading: Boolean = false,
val errorMessage: String? = null,
val isUserLoggedIn: Boolean = false
)
এই UI isUserLoggedIn
অবস্থার পরিবর্তনগুলিতে প্রতিক্রিয়া জানায় এবং প্রয়োজন অনুসারে সঠিক গন্তব্যে নেভিগেট করে:
ভিউ
class LoginViewModel : ViewModel() {
private val _uiState = MutableStateFlow(LoginUiState())
val uiState: StateFlow<LoginUiState> = _uiState.asStateFlow()
/* ... */
}
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
if (uiState.isUserLoggedIn) {
// Navigate to the Home screen.
}
...
}
}
}
}
}
রচনা করুন
class LoginViewModel : ViewModel() {
var uiState by mutableStateOf(LoginUiState())
private set
/* ... */
}
@Composable
fun LoginScreen(
viewModel: LoginViewModel = viewModel(),
onUserLogIn: () -> Unit
) {
val currentOnUserLogIn by rememberUpdatedState(onUserLogIn)
// Whenever the uiState changes, check if the user is logged in.
LaunchedEffect(viewModel.uiState) {
if (viewModel.uiState.isUserLoggedIn) {
currentOnUserLogIn()
}
}
// Rest of the UI for the login screen.
}
গ্রাসকারী ঘটনা রাষ্ট্র আপডেট ট্রিগার করতে পারে
UI-তে নির্দিষ্ট ViewModel ইভেন্টগুলি ব্যবহার করার ফলে অন্যান্য UI স্টেট আপডেট হতে পারে। উদাহরণস্বরূপ, যখন ব্যবহারকারীকে কিছু ঘটেছে তা জানাতে স্ক্রীনে ক্ষণস্থায়ী বার্তাগুলি দেখানোর সময়, যখন বার্তাটি স্ক্রিনে দেখানো হয় তখন UI-কে অন্য স্টেট আপডেট ট্রিগার করতে 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() {
private val _uiState = MutableStateFlow(LatestNewsUiState(isLoading = true))
val uiState: StateFlow<LatestNewsUiState> = _uiState
fun refreshNews() {
viewModelScope.launch {
// If there isn't internet connection, show a new message on the screen.
if (!internetConnection()) {
_uiState.update { currentUiState ->
currentUiState.copy(userMessage = "No Internet connection")
}
return@launch
}
// Do something else.
}
}
fun userMessageShown() {
_uiState.update { currentUiState ->
currentUiState.copy(userMessage = null)
}
}
}
রচনা করুন
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)
}
}
ViewModel-এর জানার দরকার নেই যে UI কীভাবে বার্তাটি স্ক্রিনে দেখাচ্ছে; এটা শুধু জানে যে একটি ব্যবহারকারীর বার্তা আছে যা দেখানো দরকার। একবার ক্ষণস্থায়ী বার্তাটি দেখানো হয়ে গেলে, UI-কে সেটির ViewModelকে অবহিত করতে হবে, যার ফলে userMessage
প্রপার্টি সাফ করার জন্য অন্য UI স্টেট আপডেট হবে:
ভিউ
class LatestNewsActivity : AppCompatActivity() {
private val viewModel: LatestNewsViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
uiState.userMessage?.let {
// TODO: Show Snackbar with userMessage.
// Once the message is displayed and
// dismissed, notify the ViewModel.
viewModel.userMessageShown()
}
...
}
}
}
}
}
রচনা করুন
@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 অবস্থা ব্যবহার করেন তার বিবরণ রাজ্য আপডেট বিভাগকে ট্রিগার করতে পারে । নেভিগেশন ইভেন্টগুলিও একটি Android অ্যাপে একটি সাধারণ ধরনের ইভেন্ট।
ব্যবহারকারী একটি বোতামে ট্যাপ করার কারণে যদি UI-তে ইভেন্টটি ট্রিগার করা হয়, তাহলে UI ন্যাভিগেশন কন্ট্রোলারকে কল করে বা উপযুক্ত হিসাবে কলারের কাছে ইভেন্টটি প্রকাশ করে সেটির যত্ন নেয়।
ভিউ
class LoginActivity : AppCompatActivity() {
private lateinit var binding: ActivityLoginBinding
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
binding.helpButton.setOnClickListener {
navController.navigate(...) // Open help screen
}
}
}
রচনা করুন
@Composable
fun LoginScreen(
onHelp: () -> Unit, // Caller navigates to the right screen
viewModel: LoginViewModel = viewModel()
) {
// Rest of the UI
Button(onClick = onHelp) {
Text("Get help")
}
}
যদি ডেটা ইনপুট নেভিগেট করার আগে কিছু ব্যবসায়িক যুক্তি যাচাইকরণের প্রয়োজন হয়, তাহলে ViewModel-কে সেই অবস্থাটি UI-তে প্রকাশ করতে হবে। UI সেই অবস্থার পরিবর্তনে প্রতিক্রিয়া জানাবে এবং সেই অনুযায়ী নেভিগেট করবে। হ্যান্ডেল ভিউমডেল ইভেন্ট বিভাগটি এই ব্যবহারের ক্ষেত্রে কভার করে। এখানে একটি অনুরূপ কোড আছে:
ভিউ
class LoginActivity : AppCompatActivity() {
private val viewModel: LoginViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
/* ... */
lifecycleScope.launch {
repeatOnLifecycle(Lifecycle.State.STARTED) {
viewModel.uiState.collect { uiState ->
if (uiState.isUserLoggedIn) {
// Navigate to the Home screen.
}
...
}
}
}
}
}
রচনা করুন
@Composable
fun LoginScreen(
onUserLogIn: () -> Unit, // Caller navigates to the right screen
viewModel: LoginViewModel = viewModel()
) {
Button(
onClick = {
// ViewModel validation is triggered
viewModel.login()
}
) {
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()
}
}
}
উপরের উদাহরণে, অ্যাপটি প্রত্যাশিত হিসাবে কাজ করে কারণ বর্তমান গন্তব্য, লগইন, ব্যাক স্ট্যাকের মধ্যে রাখা হবে না। ব্যবহারকারীরা ফিরে চাপলে এটিতে ফিরে যেতে পারবেন না। যাইহোক, যে ক্ষেত্রে এটি ঘটতে পারে, সমাধানের জন্য অতিরিক্ত যুক্তির প্রয়োজন হবে।
নেভিগেশন ইভেন্টগুলি যখন গন্তব্যটি পিছনের স্ট্যাকে রাখা হয়
যখন একটি ভিউমডেল এমন কিছু অবস্থা সেট করে যা স্ক্রীন A থেকে স্ক্রীন B তে একটি নেভিগেশন ইভেন্ট তৈরি করে এবং স্ক্রীন A নেভিগেশন ব্যাক স্ট্যাকে রাখা হয়, তখন আপনাকে স্বয়ংক্রিয়ভাবে B-তে অগ্রসর না হওয়ার জন্য অতিরিক্ত যুক্তির প্রয়োজন হতে পারে। এটি বাস্তবায়ন করতে, এটির অতিরিক্ত প্রয়োজন। রাজ্য যা নির্দেশ করে যে UI অন্য স্ক্রিনে নেভিগেট করার কথা বিবেচনা করবে কিনা। সাধারণত, সেই অবস্থাটি UI-তে রাখা হয় কারণ নেভিগেশন লজিক হল UI-এর উদ্বেগ, ভিউমডেল নয়। এটি ব্যাখ্যা করার জন্য, আসুন নিম্নলিখিত ব্যবহারের ক্ষেত্রে বিবেচনা করা যাক।
ধরা যাক যে আপনি আপনার অ্যাপের নিবন্ধন প্রবাহে আছেন। জন্ম তারিখের বৈধতা স্ক্রিনে, ব্যবহারকারী যখন একটি তারিখ ইনপুট করে, ব্যবহারকারী "চালিয়ে যান" বোতামে ট্যাপ করলে ViewModel দ্বারা তারিখটি যাচাই করা হয়। ViewModel ডেটা স্তরে বৈধতা যুক্তি অর্পণ করে। তারিখটি বৈধ হলে, ব্যবহারকারী পরবর্তী স্ক্রিনে যায়। একটি অতিরিক্ত বৈশিষ্ট্য হিসাবে, ব্যবহারকারীরা কিছু ডেটা পরিবর্তন করতে চাইলে বিভিন্ন রেজিস্ট্রেশন স্ক্রিনের মধ্যে পিছনে যেতে পারেন। অতএব, নিবন্ধন প্রবাহের সমস্ত গন্তব্য একই ব্যাক স্ট্যাকে রাখা হয়। এই প্রয়োজনীয়তাগুলি প্রদত্ত, আপনি এই স্ক্রীনটি নিম্নরূপ বাস্তবায়ন করতে পারেন:
ভিউ
// Key that identifies the `validationInProgress` state in the Bundle
private const val DOB_VALIDATION_KEY = "dobValidationKey"
class DobValidationFragment : Fragment() {
private var validationInProgress: Boolean = false
private val viewModel: DobValidationViewModel by viewModels()
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
val binding = // ...
validationInProgress = savedInstanceState?.getBoolean(DOB_VALIDATION_KEY) ?: false
binding.continueButton.setOnClickListener {
viewModel.validateDob()
validationInProgress = true
}
viewLifecycleOwner.lifecycleScope.launch {
viewModel.uiState
.flowWithLifecycle(viewLifecycleOwner.lifecycle)
.collect { uiState ->
// Update other parts of the UI ...
// If the input is valid and the user wants
// to navigate, navigate to the next screen
// and reset `validationInProgress` flag
if (uiState.isDobValid && validationInProgress) {
validationInProgress = false
navController.navigate(...) // Navigate to next screen
}
}
}
return binding
}
override fun onSaveInstanceState(outState: Bundle) {
super.onSaveInstanceState(outState)
outState.putBoolean(DOB_VALIDATION_KEY, validationInProgress)
}
}
রচনা করুন
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 অগ্রভাগে থাকে তখন রাজ্যের ব্যবহার বিবেচনা করুন৷
নমুনা
নিম্নলিখিত Google নমুনাগুলি UI স্তরে UI ইভেন্টগুলি প্রদর্শন করে৷ অনুশীলনে এই নির্দেশিকা দেখতে তাদের অন্বেষণ করুন:
আপনার জন্য প্রস্তাবিত
- দ্রষ্টব্য: জাভাস্ক্রিপ্ট বন্ধ থাকলে লিঙ্ক টেক্সট প্রদর্শিত হয়
- UI স্তর
- রাজ্য ধারক এবং UI রাজ্য {:#mad-arch}
- অ্যাপ আর্কিটেকচারের জন্য গাইড