Compose के यूज़र इंटरफ़ेस (यूआई) का आर्किटेक्चर

Compose में यूज़र इंटरफ़ेस (यूआई) में बदलाव नहीं किया जा सकता. इसे बनाने के बाद, अपडेट करने का कोई तरीका नहीं है. आपके पास अपने यूज़र इंटरफ़ेस (यूआई) की स्थिति को कंट्रोल करने का विकल्प होता है. यूज़र इंटरफ़ेस (यूआई) की स्थिति में बदलाव होने पर, Compose यूज़र इंटरफ़ेस (यूआई) ट्री के उन हिस्सों को फिर से बनाता है जिनमें बदलाव हुआ है. कंपोज़ेबल, स्टेट को स्वीकार कर सकते हैं और इवेंट को एक्सपोज़ कर सकते हैं. उदाहरण के लिए, TextField किसी वैल्यू को स्वीकार करता है और कॉलबैक onValueChange को एक्सपोज़ करता है. यह कॉलबैक, कॉलबैक हैंडलर से वैल्यू बदलने का अनुरोध करता है.

var name by remember { mutableStateOf("") }
OutlinedTextField(
    value = name,
    onValueChange = { name = it },
    label = { Text("Name") }
)

कंपोज़ेबल, स्टेट को स्वीकार करते हैं और इवेंट को एक्सपोज़ करते हैं. इसलिए, यूनिडायरेक्शनल डेटा फ़्लो पैटर्न, Jetpack Compose के साथ अच्छी तरह से काम करता है. इस गाइड में, Compose में यूनिडायरेक्शनल डेटा फ़्लो पैटर्न को लागू करने का तरीका बताया गया है. साथ ही, इवेंट और स्टेट होल्डर को लागू करने और Compose में ViewModels के साथ काम करने का तरीका बताया गया है.

यूनिडाइरेक्शनल डेटा फ़्लो

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

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

  1. इवेंट: यूआई का कोई हिस्सा, इवेंट जनरेट करता है और उसे ऊपर की ओर भेजता है. जैसे, बटन पर क्लिक करने के बाद, उसे हैंडल करने के लिए ViewModel को भेजा जाता है. इसके अलावा, आपके ऐप्लिकेशन की अन्य लेयर से कोई इवेंट भेजा जाता है. जैसे, यह बताना कि उपयोगकर्ता का सेशन खत्म हो गया है.
  2. स्टेट अपडेट करना: इवेंट हैंडलर, स्टेट को बदल सकता है.
  3. स्टेट दिखाना: स्टेट होल्डर, स्टेट को पास करता है और यूआई उसे दिखाता है.
इवेंट, यूज़र इंटरफ़ेस (यूआई) से स्टेट होल्डर तक जाते हैं. वहीं, स्टेट, स्टेट होल्डर से यूज़र इंटरफ़ेस (यूआई) तक जाती है.
पहली इमेज. डेटा का फ़्लो एक ही दिशा में होता है.

Jetpack Compose का इस्तेमाल करते समय इस पैटर्न को अपनाने से कई फ़ायदे मिलते हैं:

  • टेस्ट करने की सुविधा: स्टेट को यूज़र इंटरफ़ेस (यूआई) से अलग करने पर, दोनों को अलग-अलग टेस्ट करना आसान हो जाता है.
  • स्टेट एनकैप्सुलेशन: स्टेट को सिर्फ़ एक जगह पर अपडेट किया जा सकता है और कंपोज़ेबल के स्टेट के लिए सिर्फ़ एक सोर्स होता है. इसलिए, इस बात की संभावना कम होती है कि अलग-अलग स्टेट की वजह से गड़बड़ियां हों.
  • यूज़र इंटरफ़ेस (यूआई) में एक जैसा अनुभव: स्टेट में किए गए सभी अपडेट, यूज़र इंटरफ़ेस (यूआई) में तुरंत दिखते हैं. इसके लिए, StateFlow या LiveData जैसे ऑब्ज़र्वेबल स्टेट होल्डर का इस्तेमाल किया जाता है.

Jetpack Compose में यूनिडायरेक्शनल डेटा फ़्लो

कंपोज़ेबल, स्टेट और इवेंट के आधार पर काम करते हैं. उदाहरण के लिए, TextField को सिर्फ़ तब अपडेट किया जाता है, जब उसका value पैरामीटर अपडेट किया जाता है. साथ ही, यह onValueChange कॉलबैक को एक्सपोज़ करता है. यह एक ऐसा इवेंट है जो वैल्यू को नई वैल्यू में बदलने का अनुरोध करता है. Compose, State ऑब्जेक्ट को वैल्यू होल्डर के तौर पर तय करता है. साथ ही, स्टेट वैल्यू में बदलाव होने पर, फिर से कंपोज़िशन ट्रिगर होता है. स्टेट को remember { mutableStateOf(value) } या rememberSaveable { mutableStateOf(value) } में सेव किया जा सकता है. यह इस बात पर निर्भर करता है कि आपको वैल्यू को कितने समय तक सेव रखना है.

TextField कंपोज़ेबल की वैल्यू का टाइप String है. इसलिए, यह कहीं से भी आ सकती है—हार्डकोड की गई वैल्यू से, ViewModel से या पैरंट कंपोज़ेबल से. आपको इसे State ऑब्जेक्ट में रखने की ज़रूरत नहीं है. हालांकि, onValueChange को कॉल करने पर, आपको वैल्यू अपडेट करनी होगी.

  • mutableStateOf(value), MutableState बनाता है. यह Compose में ऑब्ज़र्व किया जा सकने वाला टाइप है. इसकी वैल्यू में कोई भी बदलाव होने पर, उस वैल्यू को पढ़ने वाले किसी भी कंपोज़ेबल फ़ंक्शन को फिर से कंपोज़ करने के लिए शेड्यूल किया जाता है.
  • remember, कंपोज़िशन में ऑब्जेक्ट सेव करता है. साथ ही, जब remember को कॉल करने वाले कंपोज़ेबल को कंपोज़िशन से हटा दिया जाता है, तो यह ऑब्जेक्ट को भूल जाता है.
  • rememberSaveable, कॉन्फ़िगरेशन में होने वाले बदलावों के दौरान स्टेट को सेव रखता है. इसके लिए, वह स्टेट को Bundle में सेव करता है.

कंपोज़ेबल पैरामीटर तय करना

किसी कंपोज़ेबल के स्टेट पैरामीटर तय करते समय, इन बातों का ध्यान रखें:

  • कंपोज़ेबल को फिर से इस्तेमाल करना या उसमें बदलाव करना कितना आसान है?
  • स्टेट पैरामीटर, इस कंपोज़ेबल की परफ़ॉर्मेंस पर कैसे असर डालते हैं?

डिकपलिंग और फिर से इस्तेमाल करने की सुविधा को बढ़ावा देने के लिए, हर कंपोज़ेबल में कम से कम जानकारी होनी चाहिए. उदाहरण के लिए, किसी समाचार लेख के हेडर को होल्ड करने के लिए कंपोज़ेबल बनाते समय, पूरे समाचार लेख के बजाय सिर्फ़ वह जानकारी पास करें जो दिखानी है:

@Composable
fun Header(title: String, subtitle: String) {
    // Recomposes when title or subtitle have changed.
}

@Composable
fun Header(news: News) {
    // Recomposes when a new instance of News is passed in.
}

कभी-कभी, अलग-अलग पैरामीटर का इस्तेमाल करने से भी परफ़ॉर्मेंस बेहतर होती है. उदाहरण के लिए, अगर News में सिर्फ़ title और subtitle से ज़्यादा जानकारी है, तो जब भी News का कोई नया इंस्टेंस Header(news) में पास किया जाएगा, तब कंपोज़ेबल फिर से कंपोज़ होगा. भले ही, title और subtitle में कोई बदलाव न हुआ हो.

पास किए जाने वाले पैरामीटर की संख्या पर ध्यान दें. किसी फ़ंक्शन में बहुत ज़्यादा पैरामीटर होने से, फ़ंक्शन को इस्तेमाल करना मुश्किल हो जाता है. इसलिए, ऐसे मामले में उन्हें किसी क्लास में ग्रुप करना बेहतर होता है.

Compose में इवेंट

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

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

स्टेट और इवेंट हैंडलर लैम्ब्डा के लिए, ऐसी वैल्यू पास करें जिन्हें बदला न जा सके. इस तरीके के ये फ़ायदे हैं:

  • इससे कोड को फिर से इस्तेमाल करने की सुविधा बेहतर होती है.
  • यह पुष्टि करना कि आपका यूज़र इंटरफ़ेस (यूआई), स्टेट की वैल्यू को सीधे तौर पर नहीं बदलता.
  • इससे, एक साथ कई काम करने से जुड़ी समस्याओं से बचा जा सकता है. ऐसा इसलिए, क्योंकि यह पक्का किया जाता है कि स्टेट को किसी दूसरे थ्रेड से बदला न जाए.
  • अक्सर, इससे कोड की जटिलता कम हो जाती है.

उदाहरण के लिए, पैरामीटर के तौर पर String और लैम्डा को स्वीकार करने वाले कंपोज़ेबल को कई कॉन्टेक्स्ट से कॉल किया जा सकता है. साथ ही, इसे कई बार इस्तेमाल किया जा सकता है. मान लें कि आपके ऐप्लिकेशन में सबसे ऊपर मौजूद ऐप्लिकेशन बार में हमेशा टेक्स्ट दिखता है और उसमें 'वापस जाएं' बटन मौजूद है. ज़्यादा सामान्य MyAppTopAppBar कंपोज़ेबल तय किया जा सकता है, जो टेक्स्ट और 'वापस जाएं' बटन के हैंडलर को पैरामीटर के तौर पर लेता है:

@Composable
fun MyAppTopAppBar(topAppBarText: String, onBackPressed: () -> Unit) {
    TopAppBar(
        title = {
            Text(
                text = topAppBarText,
                textAlign = TextAlign.Center,
                modifier = Modifier
                    .fillMaxSize()
                    .wrapContentSize(Alignment.Center)
            )
        },
        navigationIcon = {
            IconButton(onClick = onBackPressed) {
                Icon(
                    Icons.AutoMirrored.Filled.ArrowBack,
                    contentDescription = localizedString
                )
            }
        },
        // ...
    )
}

ViewModels, स्टेट, और इवेंट: एक उदाहरण

ViewModel और mutableStateOf का इस्तेमाल करके, अपने ऐप्लिकेशन में यूनिडायरेक्शनल डेटा फ़्लो भी लागू किया जा सकता है. हालांकि, इसके लिए इनमें से कोई एक शर्त पूरी होनी चाहिए:

  • आपके यूज़र इंटरफ़ेस (यूआई) की स्थिति, StateFlow या LiveData जैसे ऑब्ज़र्वेबल स्टेट होल्डर का इस्तेमाल करके दिखती है.
  • ViewModel, यूज़र इंटरफ़ेस (यूआई) या आपके ऐप्लिकेशन की अन्य लेयर से आने वाले इवेंट को मैनेज करता है. साथ ही, इवेंट के आधार पर स्टेट होल्डर को अपडेट करता है.

उदाहरण के लिए, साइन-इन स्क्रीन लागू करते समय, साइन इन करें बटन पर टैप करने से, आपके ऐप्लिकेशन को प्रोग्रेस स्पिनर और नेटवर्क कॉल दिखानी चाहिए. अगर लॉगिन हो जाता है, तो आपका ऐप्लिकेशन दूसरी स्क्रीन पर चला जाता है. गड़बड़ी होने पर, ऐप्लिकेशन एक स्नैकबार दिखाता है. स्क्रीन की स्थिति और इवेंट को मॉडल करने का तरीका यहां बताया गया है:

स्क्रीन की चार स्थितियां होती हैं:

  • साइन आउट किया गया: जब उपयोगकर्ता ने अब तक साइन इन नहीं किया है.
  • In progress: जब आपका ऐप्लिकेशन, नेटवर्क कॉल करके उपयोगकर्ता को साइन इन कराने की कोशिश कर रहा हो.
  • गड़बड़ी: साइन इन करते समय कोई गड़बड़ी होने पर.
  • साइन इन किया हुआ: जब उपयोगकर्ता ने साइन इन किया हो.

इन स्थितियों को सील की गई क्लास के तौर पर मॉडल किया जा सकता है. ViewModel, स्टेट को State के तौर पर दिखाता है. साथ ही, शुरुआती स्टेट सेट करता है और ज़रूरत के हिसाब से स्टेट को अपडेट करता है. ViewModel, onSignIn() तरीके को एक्सपोज़ करके, साइन-इन इवेंट को भी मैनेज करता है.

class MyViewModel : ViewModel() {
    private val _uiState = mutableStateOf<UiState>(UiState.SignedOut)
    val uiState: State<UiState>
        get() = _uiState

    // ...
}

mutableStateOf एपीआई के अलावा, Compose LiveData, Flow, और Observable के लिए एक्सटेंशन उपलब्ध कराता है, ताकि उन्हें लिसनर के तौर पर रजिस्टर किया जा सके और वैल्यू को स्टेट के तौर पर दिखाया जा सके.

class MyViewModel : ViewModel() {
    private val _uiState = MutableLiveData<UiState>(UiState.SignedOut)
    val uiState: LiveData<UiState>
        get() = _uiState

    // ...
}

@Composable
fun MyComposable(viewModel: MyViewModel) {
    val uiState = viewModel.uiState.observeAsState()
    // ...
}

ज़्यादा जानें

Jetpack Compose में आर्किटेक्चर के बारे में ज़्यादा जानने के लिए, ये संसाधन देखें:

सैंपल