राज्य को कहां जगाएं

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

सबसे सही तरीका

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

सबसे निचला कॉमन ऐंसेस्टर, कंपोज़िशन के बाहर भी हो सकता है. उदाहरण के लिए, कारोबारी नियम शामिल होने की वजह से, ViewModel में स्टेट को होस्ट करते समय.

इस पेज पर, इस सबसे सही तरीके के बारे में विस्तार से बताया गया है. साथ ही, ध्यान रखने वाली एक चेतावनी भी दी गई है.

यूज़र इंटरफ़ेस (यूआई) स्टेट और यूज़र इंटरफ़ेस (यूआई) लॉजिक के टाइप

इस दस्तावेज़ में इस्तेमाल किए गए यूज़र इंटरफ़ेस (यूआई) की स्थिति और लॉजिक के टाइप की परिभाषाएं यहां दी गई हैं.

यूज़र इंटरफ़ेस (यूआई) की स्थिति

यूज़र इंटरफ़ेस (यूआई) की स्थिति वह प्रॉपर्टी है जो यूज़र इंटरफ़ेस (यूआई) के बारे में बताती है. यूज़र इंटरफ़ेस (यूआई) की स्थिति दो तरह की होती है:

  • स्क्रीन यूज़र इंटरफ़ेस (यूआई) स्टेट वह डेटा है जो आपको स्क्रीन पर दिखाना है. उदाहरण के लिए, NewsUiState क्लास में समाचार रिपोर्ट और यूज़र इंटरफ़ेस (यूआई) को रेंडर करने के लिए ज़रूरी अन्य जानकारी शामिल हो सकती है. आम तौर पर, यह स्थिति हैरारकी की अन्य लेयर से जुड़ी होती है, क्योंकि इसमें ऐप्लिकेशन का डेटा होता है.
  • यूज़र इंटरफ़ेस (यूआई) एलिमेंट की स्थिति से, यूज़र इंटरफ़ेस (यूआई) एलिमेंट की उन प्रॉपर्टी के बारे में पता चलता है जो उनके रेंडर होने के तरीके पर असर डालती हैं. यूज़र इंटरफ़ेस (यूआई) एलिमेंट को दिखाया या छिपाया जा सकता है. साथ ही, इसमें कोई खास फ़ॉन्ट, फ़ॉन्ट साइज़ या फ़ॉन्ट का रंग हो सकता है. Jetpack Compose में, स्टेट कंपोज़ेबल के बाहर होता है. इसे कंपोज़ेबल के आस-पास से हटाकर, कॉल करने वाले कंपोज़ेबल फ़ंक्शन या स्टेट होल्डर में भी होस्ट किया जा सकता है. इसका एक उदाहरण Scaffold कंपोज़ेबल के लिए, ScaffoldState है.

लॉजिक

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

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

यूज़र इंटरफ़ेस (यूआई) का लॉजिक

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

यहां दोनों समाधानों के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि किस समाधान का इस्तेमाल कब करना चाहिए.

स्टेट के मालिक के तौर पर कंपोज़ेबल

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

स्टेट होइस्टिंग की ज़रूरत नहीं है

स्टेट को होस्ट करना हमेशा ज़रूरी नहीं होता. जब किसी कंपोज़ेबल को कंट्रोल करने के लिए किसी दूसरे कंपोज़ेबल की ज़रूरत न हो, तो स्टेट को कंपोज़ेबल में इंटरनल रखा जा सकता है. इस स्निपेट में, एक कंपोज़ेबल है जो टैप करने पर बड़ा और छोटा होता है:

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) } // Define the UI element expanded state

    Text(
        text = AnnotatedString(message.content),
        modifier = Modifier.clickable {
            showDetails = !showDetails // Apply UI logic
        }
    )

    if (showDetails) {
        Text(message.timestamp)
    }
}

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

कंपोज़ेबल में होस्टिंग

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

यहां दिए गए उदाहरण में, एक चैट ऐप्लिकेशन दिखाया गया है. इसमें दो सुविधाएं लागू की गई हैं:

  • JumpToBottom बटन, मैसेज की सूची को सबसे नीचे तक स्क्रोल करता है. बटन, सूची की स्थिति के हिसाब से यूज़र इंटरफ़ेस (यूआई) लॉजिक को लागू करता है.
  • उपयोगकर्ता के नए मैसेज भेजने के बाद, MessagesList सूची नीचे की ओर स्क्रोल होती है. UserInput, सूची की स्थिति पर यूज़र इंटरफ़ेस (यूआई) लॉजिक लागू करता है.
JumpToBottom बटन वाला चैट ऐप्लिकेशन. इसमें नए मैसेज मिलने पर, सबसे नीचे स्क्रोल करने की सुविधा भी है
पहली इमेज. चैट ऐप्लिकेशन में JumpToBottom बटन और नए मैसेज मिलने पर सबसे नीचे स्क्रोल करने की सुविधा

कंपोज़ेबल हैरारकी इस तरह है:

चैट कंपोज़ेबल ट्री
दूसरी इमेज. चैट कंपोज़ेबल ट्री

LazyColumn स्टेट को बातचीत वाली स्क्रीन पर होस्ट किया जाता है, ताकि ऐप्लिकेशन यूज़र इंटरफ़ेस (यूआई) लॉजिक को लागू कर सके. साथ ही, उन सभी कंपोज़ेबल से स्टेट को पढ़ सके जिन्हें इसकी ज़रूरत है:

LazyColumn की स्थिति को LazyColumn से ConversationScreen पर होस्ट करना
तीसरी इमेज. LazyColumn स्टेट को LazyColumn से ConversationScreen में होस्ट करना

आखिर में, कंपोज़ेबल ये हैं:

ConversationScreen पर होस्ट किए गए LazyListState के साथ चैट कंपोज़ेबल ट्री
चौथी इमेज. चैट कंपोज़ेबल ट्री में LazyListState को ConversationScreen पर होस्ट किया गया है

कोड इस तरह है:

@Composable
private fun ConversationScreen(/*...*/) {
    val scope = rememberCoroutineScope()

    val lazyListState = rememberLazyListState() // State hoisted to the ConversationScreen

    MessagesList(messages, lazyListState) // Reuse same state in MessageList

    UserInput(
        onMessageSent = { // Apply UI logic to lazyListState
            scope.launch {
                lazyListState.scrollToItem(0)
            }
        },
    )
}

@Composable
private fun MessagesList(
    messages: List<Message>,
    lazyListState: LazyListState = rememberLazyListState() // LazyListState has a default value
) {

    LazyColumn(
        state = lazyListState // Pass hoisted state to LazyColumn
    ) {
        items(messages, key = { message -> message.id }) { item ->
            Message(/*...*/)
        }
    }

    val scope = rememberCoroutineScope()

    JumpToBottom(onClicked = {
        scope.launch {
            lazyListState.scrollToItem(0) // UI logic being applied to lazyListState
        }
    })
}

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

ध्यान दें कि lazyListState को MessagesList तरीके में तय किया गया है. इसकी डिफ़ॉल्ट वैल्यू rememberLazyListState() है. यह Compose में एक सामान्य पैटर्न है. इससे कंपोज़ेबल को ज़्यादा बार इस्तेमाल किया जा सकता है और उन्हें ज़्यादा सुविधाजनक बनाया जा सकता है. इसके बाद, कंपोज़ेबल का इस्तेमाल ऐप्लिकेशन के अलग-अलग हिस्सों में किया जा सकता है. ऐसा हो सकता है कि इन हिस्सों को स्टेट कंट्रोल करने की ज़रूरत न पड़े. आम तौर पर, ऐसा किसी कंपोज़ेबल की झलक देखने या उसे टेस्ट करने के दौरान होता है. LazyColumn ने अपनी स्थिति को इसी तरह परिभाषित किया है.

LazyListState के लिए सबसे निचला कॉमन ऐंसेस्टर, ConversationScreen है
पांचवीं इमेज. LazyListState के लिए सबसे निचला कॉमन ऐंसेस्टर ConversationScreen है

स्टेट ओनर के तौर पर प्लेन स्टेट होल्डर क्लास

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

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

इन सामान्य क्लास को कंपोज़िशन में बनाया और याद रखा जाता है. ये composable के लाइफ़साइकल को फ़ॉलो करते हैं. इसलिए, ये Compose लाइब्रेरी से मिले टाइप इस्तेमाल कर सकते हैं. जैसे, rememberNavController() या rememberLazyListState().

इसका एक उदाहरण LazyListState प्लेन स्टेट होल्डर क्लास है. इसे Compose में लागू किया गया है, ताकि LazyColumn या LazyRow के यूज़र इंटरफ़ेस (यूआई) की जटिलता को कंट्रोल किया जा सके.

// LazyListState.kt

@Stable
class LazyListState constructor(
    firstVisibleItemIndex: Int = 0,
    firstVisibleItemScrollOffset: Int = 0
) : ScrollableState {
    /**
     *   The holder class for the current scroll position.
     */
    private val scrollPosition = LazyListScrollPosition(
        firstVisibleItemIndex, firstVisibleItemScrollOffset
    )

    suspend fun scrollToItem(/*...*/) { /*...*/ }

    override suspend fun scroll() { /*...*/ }

    suspend fun animateScrollToItem() { /*...*/ }
}

LazyListState, LazyColumn की स्थिति को एनकैप्सुलेट करता है. यह इस यूज़र इंटरफ़ेस (यूआई) एलिमेंट के लिए, scrollPosition को सेव करता है. यह स्क्रोल की पोज़िशन में बदलाव करने के तरीके भी दिखाता है. जैसे, किसी आइटम पर स्क्रोल करना.

जैसा कि आपने देखा, कंपोज़ेबल की ज़िम्मेदारियां बढ़ने पर, स्टेट होल्डर की ज़रूरत भी बढ़ जाती है. ये ज़िम्मेदारियां यूज़र इंटरफ़ेस (यूआई) लॉजिक में हो सकती हैं या फिर सिर्फ़ ट्रैक की जाने वाली स्टेट की संख्या में हो सकती हैं.

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

कारोबारी नियम

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

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

स्टेट के मालिक के तौर पर ViewModel

Android डेवलपमेंट में AAC ViewModels के फ़ायदे, उन्हें कारोबारी नियम का ऐक्सेस देने और स्क्रीन पर प्रज़ेंटेशन के लिए ऐप्लिकेशन डेटा तैयार करने के लिए सही बनाते हैं.

ViewModel में यूज़र इंटरफ़ेस (यूआई) की स्थिति को होस्ट करने पर, उसे कंपोज़िशन से बाहर ले जाया जाता है.

ViewModel में होइस्ट किए गए स्टेट को कंपोज़िशन के बाहर सेव किया जाता है.
छठी इमेज. ViewModel पर होइस्ट किया गया स्टेट, कंपोज़िशन के बाहर सेव किया जाता है.

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

इसके बाद, ViewModel यूज़र इंटरफ़ेस (यूआई) की स्थिति के लिए, सोर्स ऑफ़ ट्रुथ और सबसे निचला कॉमन ऐंसेस्टर होता है.

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

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

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

class ConversationViewModel(
    channelId: String,
    messagesRepository: MessagesRepository
) : ViewModel() {

    val messages = messagesRepository
        .getLatestMessages(channelId)
        .stateIn(
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed(5_000),
            initialValue = emptyList()
        )

    // Business logic
    fun sendMessage(message: Message) { /* ... */ }
}

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

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

@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {

    val messages by conversationViewModel.messages.collectAsStateWithLifecycle()

    ConversationScreen(
        messages = messages,
        onSendMessage = { message: Message -> conversationViewModel.sendMessage(message) }
    )
}

@Composable
private fun ConversationScreen(
    messages: List<Message>,
    onSendMessage: (Message) -> Unit
) {

    MessagesList(messages, onSendMessage)
    /* ... */
}

प्रॉपर्टी ड्रिलिंग

“प्रॉपर्टी ड्रिलिंग” का मतलब है कि डेटा को कई नेस्ट किए गए चाइल्ड कॉम्पोनेंट से उस जगह पर पास करना जहां उन्हें पढ़ा जाता है.

Compose में प्रॉपर्टी ड्रिलिंग का एक सामान्य उदाहरण तब दिखता है, जब स्क्रीन लेवल के स्टेट होल्डर को टॉप लेवल पर इंजेक्ट किया जाता है. साथ ही, स्टेट और इवेंट को चाइल्ड कंपोज़ेबल में पास किया जाता है. इससे, ऐप्लिकेशन बनाने की सुविधा के सिग्नेचर का ओवरलोड भी जनरेट हो सकता है.

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

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

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

अगर आपको परफ़ॉर्मेंस से जुड़ी कोई समस्या मिली है, तो आपके पास स्टेट को बाद में पढ़ने का विकल्प भी होता है. ज़्यादा जानने के लिए, परफ़ॉर्मेंस से जुड़े दस्तावेज़ देखें.

यूज़र इंटरफ़ेस (यूआई) एलिमेंट की स्थिति

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

चैट ऐप्लिकेशन के उदाहरण को जारी रखते हुए, जब कोई उपयोगकर्ता @ और कोई हिंट टाइप करता है, तो ऐप्लिकेशन ग्रुप चैट में उपयोगकर्ता के सुझाव दिखाता है. ये सुझाव, डेटा लेयर से मिलते हैं. साथ ही, उपयोगकर्ता को सुझावों की सूची दिखाने के लिए, कारोबारी नियम का इस्तेमाल किया जाता है. यह सुविधा कुछ ऐसी दिखती है:

ऐसी सुविधा जो ग्रुप चैट में उपयोगकर्ता को सुझाव दिखाती है. ऐसा तब होता है, जब उपयोगकर्ता `@` और कोई हिंट टाइप करता है
सातवीं इमेज. ऐसी सुविधा जो ग्रुप चैट में उपयोगकर्ता को सुझाव दिखाती है. ऐसा तब होता है, जब उपयोगकर्ता @ और कोई हिंट टाइप करता है

इस सुविधा को लागू करने वाला ViewModel ऐसा दिखेगा:

class ConversationViewModel(/*...*/) : ViewModel() {

    // Hoisted state
    var inputMessage by mutableStateOf("")
        private set

    val suggestions: StateFlow<List<Suggestion>> =
        snapshotFlow { inputMessage }
            .filter { hasSocialHandleHint(it) }
            .mapLatest { getHandle(it) }
            .mapLatest { repository.getSuggestions(it) }
            .stateIn(
                scope = viewModelScope,
                started = SharingStarted.WhileSubscribed(5_000),
                initialValue = emptyList()
            )

    fun updateInput(newInput: String) {
        inputMessage = newInput
    }
}

inputMessage एक वैरिएबल है, जो TextField स्टेट को सेव करता है. जब भी उपयोगकर्ता नया इनपुट टाइप करता है, तो ऐप्लिकेशन suggestions जनरेट करने के लिए कारोबारी नियम को कॉल करता है.

suggestions, स्क्रीन के यूज़र इंटरफ़ेस (यूआई) की स्थिति है. इसे Compose यूज़र इंटरफ़ेस (यूआई) में, StateFlow से इकट्ठा करके इस्तेमाल किया जाता है.

Caveat

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

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

हालांकि, Compose यूज़र इंटरफ़ेस (यूआई) से viewModelScope का इस्तेमाल करके, DrawerState के close() तरीके को कॉल करने पर, IllegalStateException टाइप का रनटाइम अपवाद होता है. इसमें यह मैसेज दिखता है: “इस CoroutineContext” में MonotonicFrameClock उपलब्ध नहीं है.

इस समस्या को ठीक करने के लिए, कंपोज़िशन के दायरे में मौजूद CoroutineScope का इस्तेमाल करें. यह CoroutineContext में एक MonotonicFrameClock उपलब्ध कराता है, जो suspend फ़ंक्शन के काम करने के लिए ज़रूरी है.

इस क्रैश को ठीक करने के लिए, ViewModel में कोरूटीन के CoroutineContext को ऐसे कोरूटीन पर स्विच करें जो कंपोज़िशन के दायरे में हो. यह कुछ ऐसा दिख सकता है:

class ConversationViewModel(/*...*/) : ViewModel() {

    val drawerState = DrawerState(initialValue = DrawerValue.Closed)

    private val _drawerContent = MutableStateFlow(DrawerContent.Empty)
    val drawerContent: StateFlow<DrawerContent> = _drawerContent.asStateFlow()

    fun closeDrawer(uiScope: CoroutineScope) {
        viewModelScope.launch {
            withContext(uiScope.coroutineContext) { // Use instead of the default context
                drawerState.close()
            }
            // Fetch drawer content and update state
            _drawerContent.update { content }
        }
    }
}

// in Compose
@Composable
private fun ConversationScreen(
    conversationViewModel: ConversationViewModel = viewModel()
) {
    val scope = rememberCoroutineScope()

    ConversationScreen(onCloseDrawer = { conversationViewModel.closeDrawer(uiScope = scope) })
}

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

स्टेट और Jetpack Compose के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें.

सैंपल

कोडलैब

वीडियो