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

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

@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 OS के साथ इंटरैक्ट करने वाले अन्य ऑब्जेक्ट या पेमेंट प्रोसेस करने वाली कंपनी या विज्ञापन जैसी तीसरे पक्ष की लाइब्रेरी मैनेज करने वाले ऑब्जेक्ट.

ऐडवांस उपयोगकर्ताओं के लिए, मॉडर्न 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) }
    )
}