Compose में स्टेट के लाइफ़स्पैन

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

remember, रीकंपोज़िशन के दौरान वैल्यू को बनाए रखने के लिए एक टूल के तौर पर काम करता है. हालांकि, स्टेट को अक्सर कंपोज़िशन की लाइफ़टाइम से ज़्यादा समय तक बनाए रखने की ज़रूरत होती है. इस पेज पर, remember, retain, rememberSaveable, और rememberSerializable एपीआई के बीच अंतर के बारे में बताया गया है. साथ ही, यह भी बताया गया है कि किस एपीआई को कब चुनना चाहिए. इसके अलावा, Compose में याद रखी गई और बनाए रखी गई वैल्यू को मैनेज करने के सबसे सही तरीकों के बारे में भी बताया गया है.

सही लाइफ़स्पैन चुनना

Compose में, कई ऐसे फ़ंक्शन होते हैं जिनका इस्तेमाल करके, कंपोज़िशन और उससे आगे भी स्टेट को बनाए रखा जा सकता है: remember, retain, rememberSaveable, और rememberSerializable. इन फ़ंक्शन की लाइफ़स्पैन और सिमैंटिक अलग-अलग होते हैं. साथ ही, हर फ़ंक्शन का इस्तेमाल किसी खास तरह की स्थिति को सेव करने के लिए किया जाता है. यहां दी गई टेबल में, दोनों के बीच के अंतर बताए गए हैं:

remember

retain

rememberSaveable, rememberSerializable

क्या वैल्यू, फिर से कंपोज़ करने के बाद भी बनी रहती हैं?

क्या गतिविधि फिर से शुरू होने पर वैल्यू बनी रहती हैं?

हमेशा एक ही (===) इंस्टेंस दिखाया जाएगा

इसके बराबर (==) ऑब्जेक्ट दिखेगा. ऐसा हो सकता है कि यह डिसिरियलाइज़ की गई कॉपी हो

क्या प्रोसेस बंद होने पर वैल्यू बनी रहती हैं?

किस तरह के डेटा को ट्रांसफ़र किया जा सकता है

सभी

ऐसे किसी भी ऑब्जेक्ट का रेफ़रंस नहीं होना चाहिए जिसे ऐक्टिविटी बंद होने पर लीक किया जा सकता है

सीरियलाइज़ की जा सकने वाली होनी चाहिए
(कस्टम Saver या kotlinx.serialization के साथ)

इस्तेमाल के उदाहरण

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

remember

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

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

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

उपलब्ध विकल्पों में से, remember का लाइफ़स्पैन सबसे कम होता है. साथ ही, यह इस पेज पर बताए गए चार मेमोइज़ेशन फ़ंक्शन में से सबसे पहले वैल्यू भूल जाता है. इसलिए, यह इन कामों के लिए सबसे सही है:

  • स्क्रोल की पोज़िशन या ऐनिमेशन की स्थिति जैसे इंटरनल स्टेट ऑब्जेक्ट बनाना
  • हर बार फिर से कंपोज़ करने पर, महंगे ऑब्जेक्ट को फिर से बनाने से बचना

हालांकि, आपको इन बातों से बचना चाहिए:

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

rememberSaveable और rememberSerializable

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

rememberSerializable, rememberSaveable की तरह ही काम करता है. हालांकि, यह kotlinx.serialization लाइब्रेरी के साथ क्रम से लगाए जा सकने वाले मुश्किल टाइप को अपने-आप सेव करता है. अगर आपका टाइप @Serializable के तौर पर मार्क किया गया है (या किया जा सकता है), तो rememberSerializable चुनें. अन्य सभी मामलों में, rememberSaveable चुनें.

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

ध्यान दें कि rememberSaveable और rememberSerializable, मेमोराइज़ की गई वैल्यू को Bundle में क्रम से लगाकर सेव करते हैं. इसके दो नतीजे होते हैं:

  • मेमोइज़ की गई वैल्यू को इनमें से किसी एक या एक से ज़्यादा डेटा टाइप के ज़रिए दिखाया जा सकता है: प्रिमिटिव (इसमें Int, Long, Float, Double शामिल हैं), String या इनमें से किसी भी टाइप के ऐरे.
  • सेव की गई वैल्यू को वापस लाने पर, यह एक नया इंस्टेंस होगा, जो (==) के बराबर होगा. हालांकि, यह कंपोज़िशन के पहले इस्तेमाल किए गए रेफ़रंस (===) के जैसा नहीं होगा.

kotlinx.serialization का इस्तेमाल किए बिना, ज़्यादा जटिल डेटा टाइप सेव करने के लिए, कस्टम Saver लागू किया जा सकता है. इससे ऑब्जेक्ट को सीरियल और डीसीरियल करके, काम करने वाले डेटा टाइप में बदला जा सकता है. ध्यान दें कि Compose, सामान्य डेटा टाइप को समझता है. जैसे, State, List, Map, Set वगैरह. साथ ही, यह इन्हें अपने-आप आपके लिए काम करने वाले टाइप में बदल देता है. यहां Size क्लास के लिए Saver का एक उदाहरण दिया गया है. इसे Size का इस्तेमाल करके, Size की सभी प्रॉपर्टी को एक सूची में पैक करके लागू किया जाता है.listSaver

data class Size(val x: Int, val y: Int) {
    object Saver : androidx.compose.runtime.saveable.Saver<Size, Any> by listSaver(
        save = { listOf(it.x, it.y) },
        restore = { Size(it[0], it[1]) }
    )
}

@Composable
fun rememberSize(x: Int, y: Int) {
    rememberSaveable(x, y, saver = Size.Saver) {
        Size(x, y)
    }
}

retain

retain एपीआई, remember और rememberSaveable/rememberSerializable के बीच मौजूद है. ऐसा इसलिए, क्योंकि यह अपनी वैल्यू को मेमोराइज़ करने में इतना समय लेता है. इसका नाम अलग है, क्योंकि सेव की गई वैल्यू का लाइफ़साइकल भी, याद रखी गई वैल्यू से अलग होता है.

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

लाइफ़साइकल rememberSaveable से छोटा होने की वजह से, retain ऐसी वैल्यू को बनाए रख सकता है जिन्हें क्रम से नहीं लगाया जा सकता. जैसे, लैम्डा एक्सप्रेशन, फ़्लो, और बिटमैप जैसे बड़े ऑब्जेक्ट. उदाहरण के लिए, कॉन्फ़िगरेशन में बदलाव के दौरान मीडिया चलाने में रुकावटों को रोकने के लिए, retain का इस्तेमाल करके मीडिया प्लेयर (जैसे कि ExoPlayer) को मैनेज किया जा सकता है.

@Composable
fun MediaPlayer() {
    // Use the application context to avoid a memory leak
    val applicationContext = LocalContext.current.applicationContext
    val exoPlayer = retain { ExoPlayer.Builder(applicationContext).apply { /* ... */ }.build() }
    // ...
}

retain बनाम ViewModel

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

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

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

retain उन ऑब्जेक्ट के लिए सबसे सही है जो किसी खास कंपोज़ेबल इंस्टेंस के स्कोप में होते हैं. साथ ही, जिन्हें एक ही तरह के कंपोज़ेबल के बीच फिर से इस्तेमाल या शेयर करने की ज़रूरत नहीं होती. ViewModel का इस्तेमाल यूज़र इंटरफ़ेस (यूआई) की स्थिति को सेव करने और बैकग्राउंड टास्क करने के लिए किया जाता है. वहीं, retain का इस्तेमाल यूज़र इंटरफ़ेस (यूआई) के प्लंबिंग के लिए ऑब्जेक्ट सेव करने के लिए किया जाता है. जैसे, कैश मेमोरी, इंप्रेशन ट्रैकिंग और आंकड़ों, AndroidView पर निर्भरता, और Android ओएस के साथ इंटरैक्ट करने वाले अन्य ऑब्जेक्ट या पेमेंट प्रोसेसर या विज्ञापन जैसी तीसरे पक्ष की लाइब्रेरी मैनेज करने वाले ऑब्जेक्ट.

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

retain

ViewModel

स्कोपिंग

कोई भी वैल्यू शेयर नहीं की जाती; हर वैल्यू को कंपोज़िशन के पदानुक्रम में किसी खास पॉइंट पर बनाए रखा जाता है और उससे जोड़ा जाता है. किसी दूसरी जगह पर एक ही तरह का डेटा बनाए रखने से, हमेशा एक नया इंस्टेंस बनता है.

ViewModel, ViewModelStore में सिंगलटन होते हैं

डेटा को मिटाना

कंपोज़िशन के क्रम से हमेशा के लिए बाहर निकलने पर

जब ViewModelStore को मिटा दिया जाता है या हटा दिया जाता है

अतिरिक्त सुविधाएँ

यह ऑब्जेक्ट, कंपोज़िशन हैरारकी में है या नहीं, इस बारे में कॉलबैक मिल सकते हैं

बिल्ट-इन coroutineScope, SavedStateHandle के लिए सहायता, जिसे Hilt का इस्तेमाल करके इंजेक्ट किया जा सकता है

मालिकाना हक

RetainedValuesStore

ViewModelStore

इस्तेमाल के उदाहरण

  • यूज़र इंटरफ़ेस (यूआई) से जुड़ी वैल्यू को अलग-अलग कंपोज़ेबल इंस्टेंस के लिए स्थानीय तौर पर सेव करना
  • इंप्रेशन ट्रैकिंग, शायद RetainedEffect के ज़रिए
  • कस्टम "ViewModel-like" आर्किटेक्चर कॉम्पोनेंट को तय करने के लिए बिल्डिंग ब्लॉक
  • यूज़र इंटरफ़ेस (यूआई) और डेटा लेयर के बीच इंटरैक्शन को एक अलग क्लास में एक्सट्रैक्ट करना. ऐसा कोड को व्यवस्थित करने और उसकी जांच करने, दोनों के लिए किया जाता है
  • Flow को State ऑब्जेक्ट में बदलना और ऐसे सस्पेंड फ़ंक्शन को कॉल करना जिन्हें कॉन्फ़िगरेशन में बदलावों की वजह से रोका नहीं जाना चाहिए
  • पूरी स्क्रीन जैसे बड़े यूज़र इंटरफ़ेस (यूआई) एरिया पर शेयरिंग की स्थितियां
  • View के साथ इंटरोऑपरेबिलिटी

retain और rememberSaveable या rememberSerializable को मिलाकर इस्तेमाल करें

कभी-कभी, किसी ऑब्जेक्ट के लिए retained और rememberSaveable या rememberSerializable, दोनों के लाइफ़स्पैन का हाइब्रिड होना ज़रूरी होता है. यह इस बात का संकेत हो सकता है कि आपका ऑब्जेक्ट ViewModel होना चाहिए. यह सेव की गई स्थिति को सपोर्ट कर सकता है. इसके बारे में ViewModel के लिए सेव किए गए स्टेट मॉड्यूल की गाइड में बताया गया है.

retain और rememberSaveable या rememberSerializable को एक साथ इस्तेमाल किया जा सकता है. दोनों लाइफ़साइकल को सही तरीके से जोड़ने पर, काफ़ी मुश्किल हो सकती है. हमारा सुझाव है कि इस पैटर्न का इस्तेमाल, ज़्यादा बेहतर और कस्टम आर्किटेक्चर पैटर्न के हिस्से के तौर पर करें. साथ ही, इसका इस्तेमाल सिर्फ़ तब करें, जब ये सभी शर्तें पूरी हों:

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

इन सभी मामलों में, हमारा सुझाव है कि आप अपनी क्लास को तीन हिस्सों में बांटें: सेव किया गया डेटा, बनाए रखा गया डेटा, और "मीडिएटर" ऑब्जेक्ट. इस ऑब्जेक्ट का अपना कोई स्टेटस नहीं होता. यह बनाए रखे गए और सेव किए गए ऑब्जेक्ट को स्टेटस अपडेट करने के लिए सौंपता है. यह पैटर्न इस तरह दिखता है:

@Composable
fun rememberAndRetain(): CombinedRememberRetained {
    val saveData = rememberSerializable(serializer = serializer<ExtractedSaveData>()) {
        ExtractedSaveData()
    }
    val retainData = retain { ExtractedRetainData() }
    return remember(saveData, retainData) {
        CombinedRememberRetained(saveData, retainData)
    }
}

@Serializable
data class ExtractedSaveData(
    // All values that should persist process death should be managed by this class.
    var savedData: AnotherSerializableType = defaultValue()
)

class ExtractedRetainData {
    // All values that should be retained should appear in this class.
    // It's possible to manage a CoroutineScope using RetainObserver.
    // See the full sample for details.
    var retainedData = Any()
}

class CombinedRememberRetained(
    private val saveData: ExtractedSaveData,
    private val retainData: ExtractedRetainData,
) {
    fun doAction() {
        // Manipulate the retained and saved state as needed.
    }
}

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

इस पैटर्न को लागू करने का पूरा उदाहरण देखने के लिए, पूरा सैंपल (RetainAndSaveSample.kt) देखें.

पोज़ीशनल मेमोइज़ेशन और अडैप्टिव लेआउट

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

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

ListDetailPaneScaffold और NavDisplay (Jetpack Navigation 3 से) जैसे आउट-ऑफ़-द-बॉक्स कॉम्पोनेंट के लिए, यह समस्या नहीं है. साथ ही, लेआउट में बदलाव होने पर भी आपकी स्थिति बनी रहेगी. फ़ॉर्म फ़ैक्टर के हिसाब से ढलने वाले कस्टम कॉम्पोनेंट के लिए, पक्का करें कि लेआउट में बदलाव होने से स्टेट पर कोई असर न पड़े. इसके लिए, इनमें से कोई एक काम करें:

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

फ़ैक्ट्री फ़ंक्शन याद रखें

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

Compose पर फ़ोकस करने वाले ऑब्जेक्ट तय करते समय, हमारा सुझाव है कि आप remember फ़ंक्शन बनाएं. इससे, यह तय किया जा सकेगा कि ऑब्जेक्ट को कब तक याद रखना है. इसमें लाइफ़स्पैन और मुख्य इनपुट, दोनों शामिल हैं. इससे आपके राज्य के उपभोक्ताओं को कंपोज़िशन हैरारकी में ऐसे इंस्टेंस बनाने में मदद मिलती है जो उम्मीद के मुताबिक बने रहेंगे और अमान्य हो जाएंगे. कंपोज़ेबल फ़ैक्ट्री फ़ंक्शन तय करते समय, इन दिशा-निर्देशों का पालन करें:

  • फ़ंक्शन के नाम से पहले remember लगाएं. अगर फ़ंक्शन को लागू करने के लिए, ऑब्जेक्ट के retained होने की ज़रूरत है और एपीआई कभी भी remember के किसी दूसरे वैरिएशन पर निर्भर नहीं होगा, तो retain प्रीफ़िक्स का इस्तेमाल करें. हालांकि, ऐसा करना ज़रूरी नहीं है.
  • अगर स्टेट परसिस्टेंस चुना गया है और सही Saver लागू किया जा सकता है, तो rememberSaveable या rememberSerializable का इस्तेमाल करें.
  • CompositionLocal के आधार पर साइड इफ़ेक्ट या वैल्यू शुरू करने से बचें. ऐसा हो सकता है कि ये वैल्यू, इस्तेमाल के लिए काम की न हों. ध्यान रखें कि जिस जगह पर आपका स्टेट बनाया गया है वह जगह, स्टेट के इस्तेमाल की जगह से अलग हो सकती है.

@Composable
fun rememberImageState(
    imageUri: String,
    initialZoom: Float = 1f,
    initialPanX: Int = 0,
    initialPanY: Int = 0
): ImageState {
    return rememberSaveable(imageUri, saver = ImageState.Saver) {
        ImageState(
            imageUri, initialZoom, initialPanX, initialPanY
        )
    }
}

data class ImageState(
    val imageUri: String,
    val zoom: Float,
    val panX: Int,
    val panY: Int
) {
    object Saver : androidx.compose.runtime.saveable.Saver<ImageState, Any> by listSaver(
        save = { listOf(it.imageUri, it.zoom, it.panX, it.panY) },
        restore = { ImageState(it[0] as String, it[1] as Float, it[2] as Int, it[3] as Int) }
    )
}