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 का एक उदाहरण दिया गया है. इसे लागू करने के लिए, listSaver का इस्तेमाल करके Size की सभी प्रॉपर्टी को एक सूची में शामिल किया जाता है.

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

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

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

  • पक्का करें कि स्टेटफ़ुल कंपोज़ेबल को कंपोज़िशन की हैरारकी में हमेशा एक ही जगह पर कॉल किया जाए. अडैप्टिव लेआउट लागू करने के लिए, लेआउट लॉजिक में बदलाव करें. कंपोज़िशन की हैरारकी में ऑब्जेक्ट की जगह बदलने की ज़रूरत नहीं है.
  • स्टेटफ़ुल कंपोज़ेबल को आसानी से दूसरी जगह ले जाने के लिए, 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) }
    )
}