यूज़र इंटरफ़ेस (यूआई) इवेंट

यूज़र इंटरफ़ेस (यूआई) इवेंट ऐसी कार्रवाइयां होती हैं जिन्हें यूज़र इंटरफ़ेस (यूआई) लेयर में हैंडल किया जाना चाहिए. इन्हें यूज़र इंटरफ़ेस (यूआई) या ViewModel, दोनों में से कोई भी हैंडल कर सकता है. सबसे आम तरह के इवेंट, उपयोगकर्ता इवेंट होते हैं. उपयोगकर्ता, ऐप्लिकेशन से इंटरैक्ट करके उपयोगकर्ता इवेंट जनरेट करता है. उदाहरण के लिए, स्क्रीन पर टैप करके या जेस्चर जनरेट करके. इसके बाद, यूज़र इंटरफ़ेस (यूआई) इन इवेंट का इस्तेमाल करता है. इसके लिए, वह onClick() लिसनर जैसे कॉलबैक का इस्तेमाल करता है.

ViewModel, आम तौर पर किसी उपयोगकर्ता के इवेंट की बिज़नेस लॉजिक को हैंडल करने के लिए ज़िम्मेदार होता है. उदाहरण के लिए, उपयोगकर्ता का कुछ डेटा रीफ़्रेश करने के लिए किसी बटन पर क्लिक करना. आम तौर पर, ViewModel, यूज़र इंटरफ़ेस (यूआई) को कॉल किए जा सकने वाले फ़ंक्शन उपलब्ध कराकर, इसे मैनेज करता है. उपयोगकर्ता के इवेंट में, यूज़र इंटरफ़ेस (यूआई) के व्यवहार से जुड़ा लॉजिक भी हो सकता है. यूज़र इंटरफ़ेस (यूआई) इसे सीधे तौर पर हैंडल कर सकता है. उदाहरण के लिए, किसी दूसरी स्क्रीन पर नेविगेट करना या Snackbar दिखाना.

अलग-अलग मोबाइल प्लैटफ़ॉर्म या फ़ॉर्म फ़ैक्टर पर एक ही ऐप्लिकेशन के लिए, बिज़नेस लॉजिक एक जैसा रहता है. हालांकि, यूज़र इंटरफ़ेस (यूआई) के व्यवहार का लॉजिक लागू करने से जुड़ी जानकारी है. यह जानकारी, अलग-अलग मामलों में अलग-अलग हो सकती है. यूज़र इंटरफ़ेस (यूआई) लेयर पेज में, इस तरह के लॉजिक को इस तरह से तय किया गया है:

  • बिज़नेस लॉजिक का मतलब है कि स्थिति में बदलाव होने पर क्या करना है. उदाहरण के लिए, पेमेंट करना या उपयोगकर्ता की प्राथमिकताओं को सेव करना. आम तौर पर, डोमेन और डेटा लेयर इस लॉजिक को मैनेज करते हैं. इस गाइड में, Architecture Components ViewModel क्लास का इस्तेमाल किया गया है. यह क्लास, कारोबारी लॉजिक को मैनेज करने वाली क्लास के लिए एक राय पर आधारित समाधान है.
  • यूज़र इंटरफ़ेस (यूआई) के व्यवहार का लॉजिक या यूज़र इंटरफ़ेस (यूआई) का लॉजिक, स्टेट में हुए बदलावों को दिखाने के तरीके को कहते हैं. उदाहरण के लिए, नेविगेशन का लॉजिक या उपयोगकर्ता को मैसेज दिखाने का तरीका. यूज़र इंटरफ़ेस (यूआई) इस लॉजिक को मैनेज करता है.

यूज़र इंटरफ़ेस (यूआई) इवेंट का डिसीज़न ट्री

यहां दिए गए डायग्राम में, किसी इवेंट के इस्तेमाल के उदाहरण को हैंडल करने का सबसे सही तरीका ढूंढने के लिए, डिसिज़न ट्री दिखाया गया है. इस गाइड के बाकी हिस्सों में, इन तरीकों के बारे में ज़्यादा जानकारी दी गई है.

अगर इवेंट ViewModel में शुरू हुआ है, तो यूज़र इंटरफ़ेस (यूआई) की स्थिति अपडेट करें. अगर इवेंट की शुरुआत यूज़र इंटरफ़ेस (यूआई) से हुई है और इसके लिए कारोबार के लॉजिक की ज़रूरत है, तो कारोबार के लॉजिक को 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()
        }
    }
}

लिखें

@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")
        }
    }
}

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.
                    }
                    ...
                }
            }
        }
    }
}

लिखें

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.
}

इवेंट का इस्तेमाल करने से, स्थिति की जानकारी अपडेट हो सकती है

यूज़र इंटरफ़ेस (यूआई) में कुछ 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)
        }
    }
}

लिखें

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 को यह जानने की ज़रूरत नहीं है कि यूज़र इंटरफ़ेस (यूआई) स्क्रीन पर मैसेज कैसे दिखा रहा है. इसे सिर्फ़ यह पता है कि उपयोगकर्ता को एक मैसेज दिखाना है. अस्थायी मैसेज दिखने के बाद, यूज़र इंटरफ़ेस (यूआई) को 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()
                    }
                    ...
                }
            }
        }
    }
}

लिखें

@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()
        }
    }
}

मैसेज कुछ समय के लिए दिखता है. हालांकि, यूज़र इंटरफ़ेस (यूआई) की स्थिति, हर समय स्क्रीन पर दिखने वाली चीज़ों को सही तरीके से दिखाती है. उपयोगकर्ता को मैसेज दिखेगा या नहीं दिखेगा.

इवेंट का इस्तेमाल करने से, स्थिति अपडेट हो सकती है सेक्शन में बताया गया है कि स्क्रीन पर उपयोगकर्ता के मैसेज दिखाने के लिए, यूज़र इंटरफ़ेस (यूआई) की स्थिति का इस्तेमाल कैसे किया जाता है. नेविगेशन इवेंट भी 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
        }
    }
}

लिखें

@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 को उस स्थिति को यूज़र इंटरफ़ेस (यूआई) पर दिखाना होगा. यूज़र इंटरफ़ेस (यूआई), स्थिति में हुए बदलाव के हिसाब से काम करेगा और उसी के मुताबिक नेविगेट करेगा. 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.
                    }
                    ...
                }
            }
        }
    }
}

लिखें

@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()
            }
    }
}

ऊपर दिए गए उदाहरण में, ऐप्लिकेशन उम्मीद के मुताबिक काम करता है. ऐसा इसलिए, क्योंकि मौजूदा डेस्टिनेशन, लॉगिन को बैक स्टैक में नहीं रखा जाएगा. अगर उपयोगकर्ता 'वापस जाएं' बटन दबाते हैं, तो वे इस पेज पर वापस नहीं जा सकते. हालांकि, ऐसे मामलों में समस्या हल करने के लिए, अतिरिक्त लॉजिक की ज़रूरत होगी.

जब कोई 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)
    }
}

लिखें

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 उस लॉजिक को डेटा लेयर को सौंप देगा. उपयोगकर्ता को अगली स्क्रीन पर ले जाने का लॉजिक, यूज़र इंटरफ़ेस (यूआई) लॉजिक है. ऐसा इसलिए, क्योंकि यूआई कॉन्फ़िगरेशन के आधार पर ये ज़रूरी शर्तें बदल सकती हैं. उदाहरण के लिए, अगर आपको एक ही समय में रजिस्ट्रेशन के कई चरण दिखाने हैं, तो हो सकता है कि आपको टैबलेट में अपने-आप दूसरी स्क्रीन पर न जाना हो. ऊपर दिए गए कोड में मौजूद validationInProgress वैरिएबल, इस सुविधा को लागू करता है. साथ ही, यह तय करता है कि जन्म की तारीख मान्य होने पर, यूज़र इंटरफ़ेस (यूआई) अपने-आप अगले रजिस्ट्रेशन चरण पर नेविगेट होना चाहिए या नहीं.

इस्तेमाल के अन्य उदाहरण

अगर आपको लगता है कि यूज़र इंटरफ़ेस (यूआई) इवेंट के इस्तेमाल से जुड़ी समस्या, यूज़र इंटरफ़ेस (यूआई) की स्थिति को अपडेट करके ठीक नहीं की जा सकती, तो आपको अपने ऐप्लिकेशन में डेटा फ़्लो के तरीके पर फिर से विचार करना पड़ सकता है. इन सिद्धांतों को ध्यान में रखें:

  • हर क्लास को सिर्फ़ वही काम करना चाहिए जिसके लिए वह ज़िम्मेदार है. यूज़र इंटरफ़ेस (यूआई) में, स्क्रीन के हिसाब से व्यवहार के लॉजिक को मैनेज करने की ज़िम्मेदारी होती है. जैसे, नेविगेशन कॉल, क्लिक इवेंट, और अनुमति के अनुरोध पाना. ViewModel में कारोबार से जुड़ा लॉजिक होता है. यह हैरारकी की निचली लेयर से मिले नतीजों को यूज़र इंटरफ़ेस (यूआई) की स्थिति में बदलता है.
  • सोचें कि इवेंट कहां से शुरू हुआ है. इस गाइड की शुरुआत में दिए गए फ़ैसले के ट्री का पालन करें. साथ ही, हर क्लास को वह काम करने दें जिसके लिए वह ज़िम्मेदार है. उदाहरण के लिए, अगर इवेंट यूज़र इंटरफ़ेस (यूआई) से शुरू होता है और इससे नेविगेशन इवेंट जनरेट होता है, तो उस इवेंट को यूज़र इंटरफ़ेस (यूआई) में हैंडल करना होगा. कुछ लॉजिक को ViewModel को सौंपा जा सकता है, लेकिन इवेंट को हैंडल करने का काम पूरी तरह से ViewModel को नहीं सौंपा जा सकता.
  • अगर आपके पास कई उपभोक्ता हैं और आपको इवेंट के कई बार इस्तेमाल होने की चिंता है, तो आपको अपने ऐप्लिकेशन के आर्किटेक्चर पर फिर से विचार करना पड़ सकता है. एक साथ कई उपभोक्ताओं के होने की वजह से, डिलीवर किए गए मैसेज की संख्या एक से ज़्यादा नहीं होनी चाहिए के अनुबंध को पूरा करना बहुत मुश्किल हो जाता है. इसलिए, जटिलता और बारीकी से काम करने की क्षमता बढ़ जाती है. अगर आपको यह समस्या आ रही है, तो अपनी यूज़र इंटरफ़ेस (यूआई) ट्री में उन समस्याओं को ऊपर की ओर ले जाएं. आपको क्रम में ऊपर की ओर स्कोप की गई किसी दूसरी इकाई की ज़रूरत पड़ सकती है.
  • सोचें कि आपको स्टेट का इस्तेमाल कब करना है. कुछ स्थितियों में, हो सकता है कि आपको ऐप्लिकेशन के बैकग्राउंड में होने पर, इस्तेमाल की जा रही स्थिति को बनाए न रखना हो. उदाहरण के लिए, Toast दिखाना. ऐसे मामलों में, यूज़र इंटरफ़ेस (यूआई) के फ़ोरग्राउंड में होने पर, स्टेट का इस्तेमाल करें.

सैंपल

Google के इन उदाहरणों में, यूज़र इंटरफ़ेस (यूआई) लेयर में यूज़र इंटरफ़ेस (यूआई) इवेंट दिखाए गए हैं. इन दिशा-निर्देशों को लागू करने के तरीके के बारे में जानने के लिए, इन्हें देखें: