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

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

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

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

एकतरफ़ा डेटा फ़्लो

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

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

  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 के अलावा, ज़्यादा जानकारी शामिल है, तो Header(news) में 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
                )
            }
        },
        // ...
    )
}

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

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

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

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

स्क्रीन की चार स्टेट होती हैं:

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

इन स्टेट को सील की गई क्लास के तौर पर मॉडल किया जा सकता है. 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 में आर्किटेक्चर के बारे में ज़्यादा जानने के लिए, ये संसाधन देखें:

सैंपल