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

Compose ऐप्लिकेशन में, UI state को कहां होस्ट करना है, यह इस बात पर निर्भर करता है कि UI लॉजिक या कारोबार के लॉजिक को इसकी ज़रूरत है या नहीं. इस दस्तावेज़ में, इन दो मुख्य स्थितियों के बारे में बताया गया है.

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

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

सबसे कम सामान्य पूर्वज, कंपोज़िशन से बाहर भी हो सकता है. उदाहरण के लिए, जब 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 से ConversationScreen में LazyColumn की स्थिति को ऊपर ले जाना
तीसरी इमेज. LazyColumn से ConversationScreen
तक LazyColumn स्थिति को ऊपर ले जाना

इसलिए, कंपोज़ेबल ये हैं:

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
है

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

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

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

इन सामान्य क्लास को कंपोज़िशन में बनाया और सेव किया जाता है. ये कंपोज़ेबल के लाइफ़साइकल को फ़ॉलो करते हैं. इसलिए, ये 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 को सेव करता है. यह स्क्रोल की पोज़िशन में बदलाव करने के तरीके भी दिखाता है. उदाहरण के लिए, किसी आइटम पर स्क्रोल करना.

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

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

कारोबार से जुड़ा लॉजिक

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

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

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

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

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

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

ViewModel को कंपोज़िशन के हिस्से के तौर पर सेव नहीं किया जाता. इन्हें फ़्रेमवर्क से उपलब्ध कराया जाता है. साथ ही, इन्हें ViewModelStoreOwner के दायरे में रखा जाता है. यह 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 से डेटा इकट्ठा किया जाता है.

चेतावनी

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

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

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

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

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

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 के बारे में ज़्यादा जानने के लिए, यहां दिए गए अन्य संसाधन देखें.

सैंपल

कोडलैब

वीडियो