RememberObserver और RetainObserver की मदद से, कंपोज़ स्टेट कॉलबैक

Jetpack Compose में, कोई ऑब्जेक्ट RememberObserver को लागू कर सकता है, ताकि remember के साथ इस्तेमाल किए जाने पर उसे कॉलबैक मिल सकें. इससे यह पता चलता है कि कंपोज़िशन हैरारकी में ऑब्जेक्ट को कब याद किया जाता है और कब नहीं. इसी तरह, retain के साथ इस्तेमाल किए गए किसी ऑब्जेक्ट की स्थिति के बारे में जानकारी पाने के लिए, RetainObserver का इस्तेमाल किया जा सकता है.

कंपोज़िशन हैरारकी से लाइफ़साइकल की इस जानकारी का इस्तेमाल करने वाले ऑब्जेक्ट के लिए, हम कुछ सबसे सही तरीके अपनाने का सुझाव देते हैं. इससे यह पुष्टि की जा सकेगी कि आपके ऑब्जेक्ट, प्लैटफ़ॉर्म पर अच्छी तरह से काम कर रहे हैं और उनका गलत इस्तेमाल नहीं किया जा रहा है. खास तौर पर, कंस्ट्रक्टर के बजाय काम शुरू करने के लिए onRemembered (या onRetained) कॉलबैक का इस्तेमाल करें. साथ ही, जब ऑब्जेक्ट याद नहीं रखे जाते या बनाए नहीं रखे जाते, तब सभी काम रद्द करें. इसके अलावा, गलती से होने वाले कॉल से बचने के लिए, RememberObserver और RetainObserver के इंप्लिमेंटेशन को लीक होने से रोकें. अगले सेक्शन में, इन सुझावों के बारे में ज़्यादा जानकारी दी गई है.

RememberObserver और RetainObserver की मदद से, डेटा लेयर में इवेंट बनाने की प्रोसेस और डेटा लेयर को साफ़ करना

Compose के बारे में सोचने के तरीके से जुड़ी गाइड में, कंपोज़िशन के पीछे के मेंटल मॉडल के बारे में बताया गया है. RememberObserver और RetainObserver का इस्तेमाल करते समय, कंपोज़िशन की इन दो बातों का ध्यान रखना ज़रूरी है:

  • रीकंपोज़िशन की प्रोसेस पूरी होने में समय लग सकता है और इसे रद्द किया जा सकता है
  • सभी कंपोज़ेबल फ़ंक्शन के कोई साइड इफ़ेक्ट नहीं होने चाहिए

कंस्ट्रक्शन के दौरान नहीं, बल्कि onRemembered या onRetained के दौरान साइड इफ़ेक्ट शुरू करें

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

इसके बजाय, RememberObserver या RetainObserver लागू करते समय, पुष्टि करें कि सभी इफ़ेक्ट और लॉन्च किए गए जॉब, onRemembered कॉलबैक में भेजे गए हों. यह SideEffect एपीआई की तरह ही काम करता है. यह भी पक्का करता है कि ये इफ़ेक्ट सिर्फ़ कंपोज़िशन लागू होने पर ही काम करें. इससे, अगर रेंडरिंग को रोक दिया जाता है या उसे बाद के लिए टाल दिया जाता है, तो अनाथ जॉब और मेमोरी लीक से बचा जा सकता है.

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    init {
        // Not recommended: This will cause work to begin during composition instead of
        // with other effects. Move this into onRemembered().
        coroutineScope.launch { loadData() }
    }

    override fun onRemembered() {
        // Recommended: Move any cancellable or effect-driven work into the onRemembered
        // callback. If implementing RetainObserver, this should go in onRetained.
        coroutineScope.launch { loadData() }
    }

    private suspend fun loadData() { /* ... */ }

    // ...
}

जब कोई सुविधा काम नहीं करती, बंद हो जाती है या उसे छोड़ दिया जाता है, तब उसे हटाना

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

कंपोज़िशन को रद्द किया जा सकता है. इसलिए, RememberObserver को लागू करने वाले ऑब्जेक्ट को कंपोज़िशन में छोड़े जाने पर, खुद को भी व्यवस्थित करना होगा. किसी ऑब्जेक्ट को तब छोड़ा जाता है, जब remember उसे किसी ऐसी कंपोज़िशन में वापस भेजता है जिसे रद्द कर दिया जाता है या जो पूरी नहीं होती. (ऐसा आम तौर पर PausableComposition का इस्तेमाल करते समय होता है. हालांकि, Android Studio के कंपोज़ेबल प्रीव्यू टूलिंग के साथ हॉट रिलोड का इस्तेमाल करते समय भी ऐसा हो सकता है.)

जब किसी ऑब्जेक्ट को छोड़ दिया जाता है, तो उसे सिर्फ़ onAbandoned को कॉल करने का अनुरोध मिलता है. उसे onRemembered को कॉल करने का अनुरोध नहीं मिलता. ऑब्जेक्ट को छोड़ने के तरीके को लागू करने के लिए, ऑब्जेक्ट के शुरू होने और onRemembered कॉलबैक मिलने के बीच बनाई गई किसी भी चीज़ को हटा दें.

class MyComposeObject : RememberObserver {
    private val job = Job()
    private val coroutineScope = CoroutineScope(Dispatchers.Main + job)

    // ...

    override fun onForgotten() {
        // Cancel work launched from onRemembered. If implementing RetainObserver, onRetired
        // should cancel work launched from onRetained.
        job.cancel()
    }

    override fun onAbandoned() {
        // If any work was launched by the constructor as part of remembering the object,
        // you must cancel that work in this callback. For work done as part of the construction
        // during retain, this code should will appear in onUnused.
        job.cancel()
    }
}

RememberObserver और RetainObserver को लागू करने की जानकारी को निजी रखना

सार्वजनिक एपीआई लिखते समय, सार्वजनिक तौर पर दिखाई जाने वाली क्लास बनाते समय RememberObserver और RetainObserver को बढ़ाते समय सावधानी बरतें. ऐसा हो सकता है कि उपयोगकर्ता को आपका ऑब्जेक्ट तब याद न आए, जब आपको उम्मीद हो. इसके अलावा, यह भी हो सकता है कि उपयोगकर्ता को आपका ऑब्जेक्ट उस तरह से याद न आए जिस तरह से आपको उम्मीद है. इस वजह से, हमारा सुझाव है कि RememberObserver या RetainObserver लागू करने वाले ऑब्जेक्ट के लिए, कंस्ट्रक्टर या फ़ैक्ट्री फ़ंक्शन को सार्वजनिक न करें. ध्यान दें कि यह किसी क्लास के रनटाइम टाइप पर निर्भर करता है, न कि एलान किए गए टाइप पर. RememberObserver या RetainObserver को लागू करने वाले ऑब्जेक्ट को याद रखने पर, उसे Any में कास्ट किया जाता है. इससे ऑब्जेक्ट को अब भी कॉलबैक मिलते हैं.

हम इसका सुझाव नहीं देते:

abstract class MyManager

// Not Recommended: Exposing a public constructor (even implicitly) for an object implementing
// RememberObserver can cause unexpected invocations if it is remembered multiple times.
class MyComposeManager : MyManager(), RememberObserver { ... }

// Not Recommended: The return type may be an implementation of RememberObserver and should be
// remembered explicitly.
fun createFoo(): MyManager = MyComposeManager()

सुझाया गया मान :

abstract class MyManager

class MyComposeManager : MyManager() {
    // Callers that construct this object must manually call initialize and teardown
    fun initialize() { /*...*/ }
    fun teardown() { /*...*/ }
}

@Composable
fun rememberMyManager(): MyManager {
    // Protect the RememberObserver implementation by never exposing it outside the library
    return remember {
        object : RememberObserver {
            val manager = MyComposeManager()
            override fun onRemembered() = manager.initialize()
            override fun onForgotten() = manager.teardown()
            override fun onAbandoned() { /* Nothing to do if manager hasn't initialized */ }
        }
    }.manager
}

ऑब्जेक्ट को याद रखने के दौरान ध्यान रखने वाली बातें

RememberObserver और RetainObserver के बारे में दिए गए पिछले सुझावों के अलावा, हमारा यह भी सुझाव है कि परफ़ॉर्मेंस और सही जवाब देने के लिए, ऑब्जेक्ट को दोबारा याद करने से बचें. यहां दिए गए सेक्शन में, याददाश्त को फिर से ट्रिगर करने वाले कुछ खास उदाहरणों के बारे में ज़्यादा जानकारी दी गई है. साथ ही, यह भी बताया गया है कि इनसे क्यों बचना चाहिए.

ऑब्जेक्ट को सिर्फ़ एक बार याद रखना

किसी ऑब्जेक्ट को फिर से याद करना खतरनाक हो सकता है. सबसे अच्छे मामले में, हो सकता है कि आप पहले से याद रखी गई वैल्यू को याद रखने के लिए संसाधनों का इस्तेमाल कर रहे हों. हालांकि, अगर कोई ऑब्जेक्ट RememberObserver लागू करता है और उसे दो बार याद किया जाता है, तो उसे उम्मीद से ज़्यादा कॉलबैक मिलेंगे. इससे समस्याएं हो सकती हैं, क्योंकि onRemembered और onForgotten लॉजिक दो बार लागू होगा. साथ ही, RememberObserver के ज़्यादातर लागू करने के तरीके इस मामले में काम नहीं करते. अगर किसी दूसरे स्कोप में दूसरा रीमेम्बर कॉल होता है और उसकी लाइफ़स्पैन, ओरिजनल remember से अलग होती है, तो RememberObserver.onForgotten के कई लागू करने के तरीके, ऑब्जेक्ट का इस्तेमाल पूरा होने से पहले ही उसे हटा देते हैं.

val first: RememberObserver = rememberFoo()

// Not Recommended: Re-remembered `Foo` now gets double callbacks
val second = remember { first }

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

val foo: Foo = rememberFoo()

// Acceptable:
val bar: Bar = remember { Bar(foo) }

// Recommended key usage:
val barWithKey: Bar = remember(foo) { Bar(foo) }

मान लें कि फ़ंक्शन के आर्ग्युमेंट पहले से याद हैं

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

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Not Recommended: Input should be remembered by the caller.
    val rememberedParameter = remember { parameter }
}

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

@Composable
fun MyComposable(
    parameter: Foo
) {
    // Acceptable:
    val derivedValue = remember { Bar(parameter) }

    // Also Acceptable:
    val derivedValueWithKey = remember(parameter) { Bar(parameter) }
}

ऐसे ऑब्जेक्ट को सेव न करें जिसे पहले से सेव किया गया है

किसी ऑब्जेक्ट को फिर से याद करने की तरह ही, आपको ऐसे ऑब्जेक्ट को बनाए रखने से बचना चाहिए जिसे याद किया गया है, ताकि उसकी लाइफ़स्पैन को बढ़ाया जा सके. यह स्टेट लाइफ़स्पैन में दी गई सलाह का नतीजा है: retain का इस्तेमाल उन ऑब्जेक्ट के साथ नहीं किया जाना चाहिए जिनका लाइफ़स्पैन, सदस्यता बनाए रखने के ऑफ़र के लाइफ़स्पैन से मेल नहीं खाता. remembered ऑब्जेक्ट का लाइफ़स्पैन, retained ऑब्जेक्ट के लाइफ़स्पैन से कम होता है. इसलिए, आपको याद किए गए ऑब्जेक्ट को बनाए नहीं रखना चाहिए. इसके बजाय, ऑब्जेक्ट को याद रखने के बजाय, उसे ओरिजनल साइट पर ही सेव करें.