सिद्धांत और Jetpack Compose को लागू करना
यूज़र इंटरफ़ेस (यूआई) इवेंट ऐसी कार्रवाइयां होती हैं जिन्हें यूज़र इंटरफ़ेस (यूआई) लेयर में हैंडल किया जाना चाहिए. इन्हें यूज़र इंटरफ़ेस (यूआई) या ViewModel, दोनों में से कोई भी हैंडल कर सकता है. सबसे आम तरह के इवेंट, उपयोगकर्ता इवेंट होते हैं. उपयोगकर्ता, ऐप्लिकेशन से इंटरैक्ट करके उपयोगकर्ता इवेंट जनरेट करता है. उदाहरण के लिए, स्क्रीन पर टैप करके या जेस्चर जनरेट करके. इसके बाद, यूज़र इंटरफ़ेस (यूआई) इन इवेंट का इस्तेमाल करता है. इसके लिए, वह onClick() लिसनर जैसे कॉलबैक का इस्तेमाल करता है.
ViewModel, आम तौर पर किसी उपयोगकर्ता के इवेंट की बिज़नेस लॉजिक को हैंडल करने के लिए ज़िम्मेदार होता है. उदाहरण के लिए, उपयोगकर्ता का कुछ डेटा रीफ़्रेश करने के लिए किसी बटन पर क्लिक करना. आम तौर पर, ViewModel, यूज़र इंटरफ़ेस (यूआई) को कॉल किए जा सकने वाले फ़ंक्शन उपलब्ध कराकर, इसे मैनेज करता है. उपयोगकर्ता के इवेंट में, यूज़र इंटरफ़ेस (यूआई) के व्यवहार से जुड़ा लॉजिक भी हो सकता है. यूज़र इंटरफ़ेस (यूआई) इसे सीधे तौर पर हैंडल कर सकता है. उदाहरण के लिए, किसी दूसरी स्क्रीन पर नेविगेट करना या Snackbar दिखाना.
अलग-अलग मोबाइल प्लैटफ़ॉर्म या फ़ॉर्म फ़ैक्टर पर एक ही ऐप्लिकेशन के लिए, बिज़नेस लॉजिक एक जैसा रहता है. हालांकि, यूज़र इंटरफ़ेस (यूआई) के व्यवहार का लॉजिक लागू करने से जुड़ी जानकारी है. यह जानकारी, अलग-अलग मामलों में अलग-अलग हो सकती है. यूज़र इंटरफ़ेस (यूआई) लेयर पेज में, इस तरह के लॉजिक को इस तरह से तय किया गया है:
- बिज़नेस लॉजिक का मतलब है कि स्थिति में बदलाव होने पर क्या करना है. उदाहरण के लिए, पेमेंट करना या उपयोगकर्ता की प्राथमिकताओं को सेव करना. आम तौर पर, डोमेन और डेटा लेयर इस लॉजिक को मैनेज करते हैं. इस गाइड में, Architecture Components ViewModel क्लास का इस्तेमाल किया गया है. यह क्लास, कारोबारी लॉजिक को मैनेज करने वाली क्लास के लिए एक राय पर आधारित समाधान है.
- यूज़र इंटरफ़ेस (यूआई) के व्यवहार का लॉजिक या यूज़र इंटरफ़ेस (यूआई) का लॉजिक, स्टेट में हुए बदलावों को दिखाने के तरीके को कहते हैं. उदाहरण के लिए, नेविगेशन का लॉजिक या उपयोगकर्ता को मैसेज दिखाने का तरीका. यूज़र इंटरफ़ेस (यूआई) इस लॉजिक को मैनेज करता है.
उपयोगकर्ता के इवेंट मैनेज करना
अगर उपयोगकर्ता के इवेंट, यूज़र इंटरफ़ेस (यूआई) एलिमेंट की स्थिति में बदलाव करने से जुड़े हैं, तो यूज़र इंटरफ़ेस (यूआई) उन इवेंट को सीधे तौर पर हैंडल कर सकता है. उदाहरण के लिए, बड़ा किए जा सकने वाले आइटम की स्थिति. अगर इवेंट के लिए, कारोबार से जुड़ी लॉजिक वाली कार्रवाई करना ज़रूरी है, जैसे कि स्क्रीन पर डेटा रीफ़्रेश करना, तो इसे ViewModel से प्रोसेस किया जाना चाहिए.
यहां दिए गए उदाहरण में दिखाया गया है कि यूज़र इंटरफ़ेस (यूआई) एलिमेंट (यूज़र इंटरफ़ेस (यूआई) लॉजिक) को बड़ा करने और स्क्रीन पर मौजूद डेटा (बिज़नेस लॉजिक) को रीफ़्रेश करने के लिए, अलग-अलग बटन का इस्तेमाल कैसे किया जाता है:
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()
}
}
}
RecyclerView में उपयोगकर्ता के इवेंट
अगर कार्रवाई यूज़र इंटरफ़ेस (यूआई) ट्री में नीचे की ओर की जाती है, जैसे कि 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 अडैप्टर, ViewModel से सीधे तौर पर इंटरैक्ट न करें.
उपयोगकर्ता इवेंट फ़ंक्शन के लिए नाम रखने के फ़ॉर्मैट
इस गाइड में, ViewModel के उन फ़ंक्शन के नाम दिए गए हैं जो उपयोगकर्ता के इवेंट हैंडल करते हैं. इनके नाम में एक क्रियावाचक शब्द होता है. यह शब्द, उस कार्रवाई के आधार पर तय होता है जिसे फ़ंक्शन हैंडल करता है. उदाहरण के लिए: addBookmark(id) या logIn(username, password).
ViewModel इवेंट हैंडल करना
ViewModel से शुरू होने वाली यूज़र इंटरफ़ेस (यूआई) कार्रवाइयों—ViewModel इवेंट—से हमेशा यूज़र इंटरफ़ेस (यूआई) की स्थिति अपडेट होनी चाहिए. यह एकतरफ़ा डेटा फ़्लो के सिद्धांतों के मुताबिक है. इससे कॉन्फ़िगरेशन में बदलाव होने के बाद भी इवेंट को फिर से जनरेट किया जा सकता है. साथ ही, यह पक्का किया जा सकता है कि यूज़र इंटरफ़ेस (यूआई) पर की गई कार्रवाइयां मिट न जाएं. अगर saved state module का इस्तेमाल किया जाता है, तो प्रोसेस बंद होने के बाद भी इवेंट को फिर से जनरेट किया जा सकता है. हालांकि, ऐसा करना ज़रूरी नहीं है.
यूज़र इंटरफ़ेस (यूआई) की कार्रवाइयों को यूज़र इंटरफ़ेस (यूआई) की स्थिति से मैप करना हमेशा आसान नहीं होता. हालांकि, इससे लॉजिक को आसान बनाने में मदद मिलती है. आपकी सोच सिर्फ़ इस बात पर खत्म नहीं होनी चाहिए कि यूज़र इंटरफ़ेस (यूआई) को किसी खास स्क्रीन पर कैसे ले जाया जाए. आपको इस बारे में और सोचना होगा. साथ ही, यह तय करना होगा कि यूज़र फ़्लो को यूज़र इंटरफ़ेस (यूआई) की स्थिति में कैसे दिखाया जाए. दूसरे शब्दों में: यह न सोचें कि यूज़र इंटरफ़ेस (यूआई) को कौनसी कार्रवाइयां करनी हैं. इसके बजाय, यह सोचें कि उन कार्रवाइयों से यूआई की स्थिति पर क्या असर पड़ता है.
उदाहरण के लिए, जब उपयोगकर्ता लॉगिन स्क्रीन पर लॉग इन हो, तब होम स्क्रीन पर जाने का तरीका देखें. यूज़र इंटरफ़ेस (यूआई) की स्थिति में इसे इस तरह से मॉडल किया जा सकता है:
data class LoginUiState(
val isLoading: Boolean = false,
val errorMessage: String? = null,
val isUserLoggedIn: Boolean = false
)
यह यूज़र इंटरफ़ेस (यूआई), 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.
}
...
}
}
}
}
}
इवेंट का इस्तेमाल करने से, स्थिति की जानकारी अपडेट हो सकती है
यूज़र इंटरफ़ेस (यूआई) में कुछ ViewModel इवेंट इस्तेमाल करने से, यूज़र इंटरफ़ेस (यूआई) की स्थिति से जुड़े अन्य अपडेट मिल सकते हैं. उदाहरण के लिए, जब स्क्रीन पर कुछ समय के लिए दिखने वाले मैसेज दिखाए जाते हैं, ताकि उपयोगकर्ता को पता चल सके कि कुछ हुआ है, तो यूज़र इंटरफ़ेस (यूआई) को ViewModel को सूचना देनी होती है. इससे, मैसेज को स्क्रीन पर दिखाए जाने के बाद, स्थिति के अपडेट को ट्रिगर किया जा सकता है. जब उपयोगकर्ता मैसेज को खारिज कर देता है या टाइमआउट के बाद मैसेज अपने-आप खारिज हो जाता है, तो इस इवेंट को "उपयोगकर्ता का इनपुट" माना जा सकता है. इसलिए, ViewModel को इस बारे में पता होना चाहिए. इस स्थिति में, यूज़र इंटरफ़ेस (यूआई) की स्थिति को इस तरह मॉडल किया जा सकता है:
// 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, यूज़र इंटरफ़ेस (यूआई) की स्थिति को इस तरह अपडेट करता है:
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)
}
}
}
ViewModel को यह जानने की ज़रूरत नहीं है कि यूज़र इंटरफ़ेस (यूआई) स्क्रीन पर मैसेज कैसे दिखा रहा है. इसे सिर्फ़ यह पता है कि उपयोगकर्ता को एक मैसेज दिखाना है. अस्थायी मैसेज दिखने के बाद, यूज़र इंटरफ़ेस (यूआई) को ViewModel को इसकी सूचना देनी होगी. इससे userMessage प्रॉपर्टी को हटाने के लिए, यूज़र इंटरफ़ेस (यूआई) की स्थिति अपडेट हो जाएगी:
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()
}
...
}
}
}
}
}
मैसेज कुछ समय के लिए दिखता है. हालांकि, यूज़र इंटरफ़ेस (यूआई) की स्थिति, हर समय स्क्रीन पर दिखने वाली चीज़ों को सही तरीके से दिखाती है. उपयोगकर्ता को मैसेज दिखेगा या नहीं दिखेगा.
नेविगेशन इवेंट
इवेंट का इस्तेमाल करने से, स्थिति अपडेट हो सकती है सेक्शन में बताया गया है कि स्क्रीन पर उपयोगकर्ता के मैसेज दिखाने के लिए, यूज़र इंटरफ़ेस (यूआई) की स्थिति का इस्तेमाल कैसे किया जाता है. नेविगेशन इवेंट भी Android ऐप्लिकेशन में होने वाले इवेंट का एक सामान्य टाइप है.
अगर उपयोगकर्ता के किसी बटन पर टैप करने की वजह से, यूज़र इंटरफ़ेस (यूआई) में इवेंट ट्रिगर होता है, तो यूआई नेविगेशन कंट्रोलर को कॉल करके इसका ध्यान रखता है.
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
}
}
}
अगर इनपुट किए गए डेटा को नेविगेट करने से पहले, कारोबार के लॉजिक की कुछ पुष्टि की ज़रूरत होती है, तो ViewModel को उस स्थिति को यूज़र इंटरफ़ेस (यूआई) पर दिखाना होगा. यूज़र इंटरफ़ेस (यूआई), स्थिति में हुए बदलाव के हिसाब से काम करेगा और उसी के मुताबिक नेविगेट करेगा. ViewModel इवेंट हैंडल करना सेक्शन में, इस इस्तेमाल के उदाहरण के बारे में बताया गया है. यहां मिलता-जुलता कोड दिया गया है:
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.
}
...
}
}
}
}
}
ऊपर दिए गए उदाहरण में, ऐप्लिकेशन उम्मीद के मुताबिक काम करता है. ऐसा इसलिए, क्योंकि मौजूदा डेस्टिनेशन, लॉगिन को बैक स्टैक में नहीं रखा जाएगा. अगर उपयोगकर्ता 'वापस जाएं' बटन दबाते हैं, तो वे इस पेज पर वापस नहीं जा सकते. हालांकि, ऐसे मामलों में समस्या हल करने के लिए, अतिरिक्त लॉजिक की ज़रूरत होगी.
जब डेस्टिनेशन को पिछली गतिविधियों में रखा जाता है, तब नेविगेशन इवेंट
जब कोई ViewModel ऐसी स्थिति सेट करता है जिससे स्क्रीन A से स्क्रीन B पर नेविगेशन इवेंट जनरेट होता है और स्क्रीन A को नेविगेशन बैक स्टैक में रखा जाता है, तो आपको B पर अपने-आप आगे बढ़ने से रोकने के लिए, अतिरिक्त लॉजिक की ज़रूरत पड़ सकती है. इसे लागू करने के लिए, एक और स्थिति की ज़रूरत होती है. इससे यह पता चलता है कि यूज़र इंटरफ़ेस (यूआई) को दूसरी स्क्रीन पर नेविगेट करना चाहिए या नहीं. आम तौर पर, उस स्थिति को यूज़र इंटरफ़ेस (यूआई) में सेव किया जाता है, क्योंकि नेविगेशन लॉजिक यूज़र इंटरफ़ेस (यूआई) से जुड़ा होता है, न कि ViewModel से. इसे समझने के लिए, इस्तेमाल का यह उदाहरण देखें.
मान लें कि आप अपने ऐप्लिकेशन के रजिस्ट्रेशन फ़्लो में हैं. जन्म की तारीख की पुष्टि करने वाली स्क्रीन पर, जब उपयोगकर्ता कोई तारीख डालता है, तो "जारी रखें" बटन पर टैप करने पर, 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)
}
}
जन्म की तारीख की पुष्टि करने का बिज़नेस लॉजिक, ViewModel के पास होता है. ज़्यादातर मामलों में, ViewModel उस लॉजिक को डेटा लेयर को सौंप देगा. उपयोगकर्ता को अगली स्क्रीन पर ले जाने का लॉजिक, यूज़र इंटरफ़ेस (यूआई) लॉजिक है. ऐसा इसलिए, क्योंकि यूआई कॉन्फ़िगरेशन के आधार पर ये ज़रूरी शर्तें बदल सकती हैं. उदाहरण के लिए, अगर आपको एक ही समय में रजिस्ट्रेशन के कई चरण दिखाने हैं, तो हो सकता है कि आपको टैबलेट में अपने-आप दूसरी स्क्रीन पर न जाना हो. ऊपर दिए गए कोड में मौजूद validationInProgress वैरिएबल, इस सुविधा को लागू करता है. साथ ही, यह तय करता है कि जन्म की तारीख मान्य होने पर, यूज़र इंटरफ़ेस (यूआई) अपने-आप अगले रजिस्ट्रेशन चरण पर नेविगेट होना चाहिए या नहीं.
आपके लिए सुझाव
- ध्यान दें: JavaScript बंद होने पर लिंक का टेक्स्ट दिखता है
- यूज़र इंटरफ़ेस (यूआई) लेयर
- स्टेट होल्डर और यूज़र इंटरफ़ेस (यूआई) स्टेट {:#mad-arch}
- ऐप्लिकेशन के आर्किटेक्चर की गाइड