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

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

किसी भी Android ऐप्लिकेशन का यूज़र इंटरफ़ेस (यूआई) स्टेट, गतिविधि या प्रोसेस को फिर से बनाने की वजह से खो सकता है. स्टेट का नुकसान इन वजहों से हो सकता है:

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

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

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

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

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

@Composable
fun ChatBubble(
    message: Message
) {
    var showDetails by rememberSaveable { mutableStateOf(false) }

    ClickableText(
        text = AnnotatedString(message.content),
        onClick = { showDetails = !showDetails }
    )

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

पहली इमेज. टैप करने पर, चैट मैसेज बबल बड़ा और छोटा होता है.

showDetails एक बूलियन वैरिएबल है. इससे यह पता चलता है कि चैट बबल छोटा है या बड़ा.

rememberSaveable सेव की गई इंस्टेंस स्टेट के मैकेनिज़्म के ज़रिए, Bundle में यूज़र इंटरफ़ेस (यूआई) एलिमेंट की स्टेट सेव करता है.

यह प्रिमिटिव टाइप को बंडल में अपने-आप सेव कर सकता है. अगर आपकी स्टेट प्रिमिटिव टाइप में नहीं है, जैसे कि डेटा क्लास, तो सेव करने के अलग-अलग मैकेनिज़्म का इस्तेमाल किया जा सकता है. जैसे, Parcelize एनोटेशन का इस्तेमाल करना, Compose APIs का इस्तेमाल करना. जैसे, listSaver और mapSaver. इसके अलावा, Compose रनटाइम Saver क्लास को बढ़ाने वाली, पसंद के मुताबिक बनाई गई सेवर क्लास को लागू करना. इन तरीकों के बारे में ज़्यादा जानने के लिए, स्टेट सेव करने के तरीके से जुड़ा दस्तावेज़ देखें.

यहां दिए गए स्निपेट में, rememberLazyListState Compose API, LazyListState को सेव करता है. इसमें LazyColumn या LazyRow की स्क्रोल स्टेट शामिल होती है. इसके लिए, rememberSaveable का इस्तेमाल किया जाता है. यह a LazyListState.Saver का इस्तेमाल करता है. यह एक कस्टम सेवर है, जो स्क्रोल स्टेट को सेव और रीस्टोर कर सकता है. गतिविधि या प्रोसेस को फिर से बनाने के बाद (उदाहरण के लिए, कॉन्फ़िगरेशन में बदलाव के बाद, जैसे कि डिवाइस का स्क्रीन की दिशा बदलना), स्क्रोल स्टेट को बनाए रखा जाता है.

@Composable
fun rememberLazyListState(
    initialFirstVisibleItemIndex: Int = 0,
    initialFirstVisibleItemScrollOffset: Int = 0
): LazyListState {
    return rememberSaveable(saver = LazyListState.Saver) {
        LazyListState(
            initialFirstVisibleItemIndex, initialFirstVisibleItemScrollOffset
        )
    }
}

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

rememberSaveable यूज़र इंटरफ़ेस (यूआई) स्टेट को सेव करने के लिए Bundle का इस्तेमाल करता है. इसे अन्य एपीआई के साथ शेयर किया जाता है. ये एपीआई भी इसमें लिखते हैं. जैसे, onSaveInstanceState() कॉल आपकी गतिविधि में. हालांकि, इस Bundle का साइज़ सीमित होता है. बड़े ऑब्जेक्ट सेव करने से, रनटाइम में TransactionTooLarge अपवाद हो सकते हैं. यह खास तौर पर, सिंगल Activity वाले ऐप्लिकेशन में समस्या पैदा कर सकता है. इनमें, पूरे ऐप्लिकेशन में एक ही Bundle का इस्तेमाल किया जाता है.

इस तरह के क्रैश से बचने के लिए, आपको बंडल में बड़े और जटिल ऑब्जेक्ट या ऑब्जेक्ट की सूचियां सेव नहीं करनी चाहिए.

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

ये डिज़ाइन विकल्प, आपके ऐप्लिकेशन के खास इस्तेमाल के उदाहरणों और उपयोगकर्ताओं की उम्मीदों पर निर्भर करते हैं.

स्टेट रीस्टोर होने की पुष्टि करना

यह पुष्टि की जा सकती है कि आपके Compose एलिमेंट में rememberSaveable के साथ सेव की गई स्टेट, गतिविधि या प्रोसेस को फिर से बनाने पर सही तरीके से रीस्टोर हो रही है. इसके लिए, खास एपीआई उपलब्ध हैं. जैसे, StateRestorationTester. ज़्यादा जानने के लिए, टेस्टिंग से जुड़ा दस्तावेज़ देखें.

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

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

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

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

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

SavedStateHandle यूज़र इंटरफ़ेस (यूआई) स्टेट को सेव करने के लिए Bundle मैकेनिज़्म का भी इस्तेमाल करता है. इसलिए, इसका इस्तेमाल सिर्फ़ यूज़र इंटरफ़ेस (यूआई) एलिमेंट की सामान्य स्टेट को सेव करने के लिए करना चाहिए.

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

SavedStateHandle एपीआई

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

Compose State saveable()
StateFlow getStateFlow()

Compose State

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

saveable API, प्रिमिटिव टाइप के साथ काम करता है. साथ ही, इसमें stateSaver पैरामीटर मिलता है. इसका इस्तेमाल करके, कस्टम सेवर का इस्तेमाल किया जा सकता है. यह rememberSaveable() की तरह ही काम करता है.

यहां दिए गए स्निपेट में, message में उपयोगकर्ता का इनपुट सेव होता है जो TextField में टाइप किया गया है:

class ConversationViewModel(
    savedStateHandle: SavedStateHandle
) : ViewModel() {

    var message by savedStateHandle.saveable(stateSaver = TextFieldValue.Saver) {
        mutableStateOf(TextFieldValue(""))
    }
        private set

    fun update(newMessage: TextFieldValue) {
        message = newMessage
    }

    /*...*/
}

val viewModel = ConversationViewModel(SavedStateHandle())

@Composable
fun UserInput(/*...*/) {
    TextField(
        value = viewModel.message,
        onValueChange = { viewModel.update(it) }
    )
}

SavedStateHandle दस्तावेज़ देखें. saveable API का इस्तेमाल करने के बारे में ज़्यादा जानने के लिए,

StateFlow

यूज़र इंटरफ़ेस (यूआई) एलिमेंट की स्टेट को सेव करने और उसे फ़्लो के तौर पर इस्तेमाल करने के लिए, SavedStateHandle से, getStateFlow() का इस्तेमाल करें. The StateFlow सिर्फ़ पढ़ने के लिए होता है, साथ ही, एपीआई के लिए ज़रूरी है कि आप एक कुंजी तय करें, ताकि नई वैल्यू एमिट करने के लिए फ़्लो को बदला जा सके. कॉन्फ़िगर की गई कुंजी की मदद से, StateFlow को वापस पाया जा सकता है और सबसे नई वैल्यू इकट्ठा की जा सकती है.

यहां दिए गए स्निपेट में, savedFilterType एक StateFlow वैरिएबल है. इसमें, चैट ऐप्लिकेशन में चैट चैनलों की सूची पर लागू किया गया फ़िल्टर टाइप सेव होता है:

private const val CHANNEL_FILTER_SAVED_STATE_KEY = "ChannelFilterKey"

class ChannelViewModel(
    channelsRepository: ChannelsRepository,
    private val savedStateHandle: SavedStateHandle
) : ViewModel() {

    private val savedFilterType: StateFlow<ChannelsFilterType> = savedStateHandle.getStateFlow(
        key = CHANNEL_FILTER_SAVED_STATE_KEY, initialValue = ChannelsFilterType.ALL_CHANNELS
    )

    private val filteredChannels: Flow<List<Channel>> =
        combine(channelsRepository.getAll(), savedFilterType) { channels, type ->
            filter(channels, type)
        }.onStart { emit(emptyList()) }

    fun setFiltering(requestType: ChannelsFilterType) {
        savedStateHandle[CHANNEL_FILTER_SAVED_STATE_KEY] = requestType
    }

    /*...*/
}

enum class ChannelsFilterType {
    ALL_CHANNELS, RECENT_CHANNELS, ARCHIVED_CHANNELS
}

उपयोगकर्ता के हर बार नया फ़िल्टर टाइप चुनने पर, setFiltering को कॉल किया जाता है. इससे, SavedStateHandle में एक नई वैल्यू सेव होती है. यह वैल्यू, _CHANNEL_FILTER_SAVED_STATE_KEY_ कुंजी के साथ सेव होती है. savedFilterType एक फ़्लो है. यह कुंजी में सेव की गई सबसे नई वैल्यू को एमिट करता है. चैनल फ़िल्टरिंग करने के लिए, filteredChannels को फ़्लो की सदस्यता मिलती है.

getStateFlow() API के बारे में ज़्यादा जानने के लिए, SavedStateHandle से जुड़ा दस्तावेज़ देखें.

खास जानकारी

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

इवेंट यूज़र इंटरफ़ेस (यूआई) लॉजिक ViewModel में कारोबारी नियम
कॉन्फ़िगरेशन में बदलाव rememberSaveable ऑटोमैटिक
सिस्टम की वजह से प्रोसेस बंद होना rememberSaveable SavedStateHandle

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

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

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