Compose में बुरा असर

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

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

स्टेट और इफ़ेक्ट के इस्तेमाल के उदाहरण

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

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

LaunchedEffect: कंपोज़ेबल के स्कोप में सस्पेंड फ़ंक्शन चलाएं

किसी कंपोज़ेबल के लाइफ़टाइम में काम करने और सस्पेंड फ़ंक्शन को कॉल करने के लिए, LaunchedEffect कंपोज़ेबल का इस्तेमाल करें. जब LaunchedEffect कंपोज़िशन में शामिल होता है, तो यह पैरामीटर के तौर पर पास किए गए कोड के ब्लॉक के साथ एक कोरूटीन लॉन्च करता है. अगर LaunchedEffect कंपोज़िशन छोड़ देता है, तो को-रूटीन रद्द हो जाएगा. अगर LaunchedEffect को अलग-अलग कुंजियों के साथ फिर से कंपोज़ किया जाता है (नीचे इफ़ेक्ट रीस्टार्ट करना सेक्शन देखें), तो मौजूदा कोरूटीन रद्द हो जाएगा. साथ ही, नया सस्पेंड फ़ंक्शन, नए कोरूटीन में लॉन्च किया जाएगा.

उदाहरण के लिए, यहां एक ऐसा ऐनिमेशन दिया गया है जो कॉन्फ़िगर किए जा सकने वाले डिले के साथ ऐल्फ़ा वैल्यू को पल्स करता है:

// Allow the pulse rate to be configured, so it can be sped up if the user is running
// out of time
var pulseRateMs by remember { mutableStateOf(3000L) }
val alpha = remember { Animatable(1f) }
LaunchedEffect(pulseRateMs) { // Restart the effect when the pulse rate changes
    while (isActive) {
        delay(pulseRateMs) // Pulse the alpha every pulseRateMs to alert the user
        alpha.animateTo(0f)
        alpha.animateTo(1f)
    }
}

ऊपर दिए गए कोड में, ऐनिमेशन को तय समय तक रोकने के लिए, सस्पेंड करने वाले फ़ंक्शन delay का इस्तेमाल किया गया है. इसके बाद, animateTo का इस्तेमाल करके, यह ऐल्फ़ा को क्रम से शून्य पर ले जाता है और फिर वापस ले आता है. यह प्रोसेस, कंपोज़ेबल के लाइफ़टाइम तक दोहराई जाएगी.

rememberCoroutineScope: कंपोज़िशन के बारे में जानकारी रखने वाला स्कोप पाएं, ताकि कंपोज़ेबल के बाहर कोई कोरूटीन लॉन्च किया जा सके

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

rememberCoroutineScope एक कंपोज़ेबल फ़ंक्शन है, जो CoroutineScope दिखाता है. यह कंपोज़िशन के उस पॉइंट से जुड़ा होता है जहां इसे कॉल किया जाता है. कॉल के कंपोज़िशन से बाहर निकलने पर, स्कोप रद्द कर दिया जाएगा.

ऊपर दिए गए उदाहरण के मुताबिक, इस कोड का इस्तेमाल करके Snackbar दिखाया जा सकता है. ऐसा तब होगा, जब उपयोगकर्ता Button पर टैप करेगा:

@Composable
fun MoviesScreen(snackbarHostState: SnackbarHostState) {

    // Creates a CoroutineScope bound to the MoviesScreen's lifecycle
    val scope = rememberCoroutineScope()

    Scaffold(
        snackbarHost = {
            SnackbarHost(hostState = snackbarHostState)
        }
    ) { contentPadding ->
        Column(Modifier.padding(contentPadding)) {
            Button(
                onClick = {
                    // Create a new coroutine in the event handler to show a snackbar
                    scope.launch {
                        snackbarHostState.showSnackbar("Something happened!")
                    }
                }
            ) {
                Text("Press me")
            }
        }
    }
}

rememberUpdatedState: किसी ऐसे इफ़ेक्ट में वैल्यू को रेफ़रंस करता है जिसे वैल्यू बदलने पर रीस्टार्ट नहीं किया जाना चाहिए

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

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

@Composable
fun LandingScreen(onTimeout: () -> Unit) {

    // This will always refer to the latest onTimeout function that
    // LandingScreen was recomposed with
    val currentOnTimeout by rememberUpdatedState(onTimeout)

    // Create an effect that matches the lifecycle of LandingScreen.
    // If LandingScreen recomposes, the delay shouldn't start again.
    LaunchedEffect(true) {
        delay(SplashWaitTimeMillis)
        currentOnTimeout()
    }

    /* Landing screen content */
}

कॉल साइट के लाइफ़साइकल से मेल खाने वाला इफ़ेक्ट बनाने के लिए, Unit या true जैसे कभी न बदलने वाले कॉन्स्टेंट को पैरामीटर के तौर पर पास किया जाता है. ऊपर दिए गए कोड में, LaunchedEffect(true) का इस्तेमाल किया गया है. यह पक्का करने के लिए कि onTimeout lambda में हमेशा वह नई वैल्यू शामिल हो जिसे LandingScreen के साथ फिर से कंपोज़ किया गया था, onTimeout को rememberUpdatedState फ़ंक्शन के साथ रैप किया जाना चाहिए. कोड में दिखाए गए State, currentOnTimeout का इस्तेमाल, इफ़ेक्ट में किया जाना चाहिए.

DisposableEffect: ऐसे इफ़ेक्ट जिन्हें हटाने की ज़रूरत है

अगर कुंजियों में बदलाव होने या कंपोज़ेबल के कंपोज़िशन से बाहर निकलने के बाद, साइड इफ़ेक्ट को हटाने की ज़रूरत है, तो DisposableEffect का इस्तेमाल करें. अगर DisposableEffect कुंजियां बदलती हैं, तो कंपोज़ेबल को अपने मौजूदा इफ़ेक्ट को डिस्पोज़ (साफ़ करना) करना होगा. साथ ही, इफ़ेक्ट को फिर से कॉल करके रीसेट करना होगा.

उदाहरण के लिए, आपको LifecycleObserver का इस्तेमाल करके, Lifecycle इवेंट के आधार पर Analytics इवेंट भेजने पड़ सकते हैं. Compose में उन इवेंट को सुनने के लिए, DisposableEffect का इस्तेमाल करें. इससे ज़रूरत पड़ने पर, ऑब्ज़र्वर को रजिस्टर और अनरजिस्टर किया जा सकता है.

@Composable
fun HomeScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit, // Send the 'started' analytics event
    onStop: () -> Unit // Send the 'stopped' analytics event
) {
    // Safely update the current lambdas when a new one is provided
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    // If `lifecycleOwner` changes, dispose and reset the effect
    DisposableEffect(lifecycleOwner) {
        // Create an observer that triggers our remembered callbacks
        // for sending analytics events
        val observer = LifecycleEventObserver { _, event ->
            if (event == Lifecycle.Event.ON_START) {
                currentOnStart()
            } else if (event == Lifecycle.Event.ON_STOP) {
                currentOnStop()
            }
        }

        // Add the observer to the lifecycle
        lifecycleOwner.lifecycle.addObserver(observer)

        // When the effect leaves the Composition, remove the observer
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }

    /* Home screen content */
}

ऊपर दिए गए कोड में, इफ़ेक्ट lifecycleOwner में observer जोड़ देगा. अगर lifecycleOwner बदलता है, तो इफ़ेक्ट हट जाता है और नए lifecycleOwner के साथ फिर से शुरू होता है.

DisposableEffect में, कोड के ब्लॉक में आखिरी स्टेटमेंट के तौर पर onDispose क्लॉज़ शामिल होना चाहिए. ऐसा न होने पर, आईडीई में बिल्ड-टाइम की गड़बड़ी दिखती है.

SideEffect: Compose की स्थिति को Compose के अलावा किसी अन्य कोड में पब्लिश करें

अगर आपको कंपोज़ेबल से मैनेज नहीं किए जा रहे ऑब्जेक्ट के साथ कंपोज़ स्टेट शेयर करनी है, तो SideEffect कंपोज़ेबल का इस्तेमाल करें. SideEffect का इस्तेमाल करने से यह पक्का होता है कि हर बार कंपोज़िशन के बाद इफ़ेक्ट लागू हो. दूसरी ओर, यह गलत है कि रीकंपोज़िशन के पूरा होने से पहले ही कोई इफ़ेक्ट लागू कर दिया जाए. ऐसा तब होता है, जब कंपोज़ेबल में सीधे तौर पर इफ़ेक्ट लिखा जाता है.

उदाहरण के लिए, आपकी Analytics लाइब्रेरी, उपयोगकर्ताओं को सेगमेंट में बांटने की सुविधा दे सकती है. इसके लिए, वह बाद के सभी Analytics इवेंट में कस्टम मेटाडेटा (इस उदाहरण में "उपयोगकर्ता प्रॉपर्टी") अटैच करती है. मौजूदा उपयोगकर्ता के टाइप की जानकारी अपनी ऐनलिटिक्स लाइब्रेरी को देने के लिए, SideEffect का इस्तेमाल करके इसकी वैल्यू अपडेट करें.

@Composable
fun rememberFirebaseAnalytics(user: User): FirebaseAnalytics {
    val analytics: FirebaseAnalytics = remember {
        FirebaseAnalytics()
    }

    // On every successful composition, update FirebaseAnalytics with
    // the userType from the current User, ensuring that future analytics
    // events have this metadata attached
    SideEffect {
        analytics.setUserProperty("userType", user.userType)
    }
    return analytics
}

produceState: गैर-कंपोज़ मोड को कंपोज़ मोड में बदलें

produceState यह कंपोज़िशन के स्कोप में एक कोरूटीन लॉन्च करता है. यह कोरूटीन, State में वैल्यू पुश कर सकता है. इसका इस्तेमाल, कंपोज़ नहीं किए जा सकने वाले कॉन्टेंट को कंपोज़ किए जा सकने वाले कॉन्टेंट में बदलने के लिए करें. उदाहरण के लिए, सदस्यता पर आधारित बाहरी कॉन्टेंट, जैसे कि Flow, LiveData या RxJava को कंपोज़िशन में शामिल करना.

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

produceState एक को-रूटीन बनाता है. हालांकि, इसका इस्तेमाल डेटा के ऐसे सोर्स को मॉनिटर करने के लिए भी किया जा सकता है जो सस्पेंड नहीं होते. उस सोर्स की सदस्यता हटाने के लिए, awaitDispose फ़ंक्शन का इस्तेमाल करें.

यहां दिए गए उदाहरण में, नेटवर्क से इमेज लोड करने के लिए produceState का इस्तेमाल करने का तरीका बताया गया है. loadNetworkImage कंपोज़ेबल फ़ंक्शन, State दिखाता है. इसका इस्तेमाल अन्य कंपोज़ेबल में किया जा सकता है.

@Composable
fun loadNetworkImage(
    url: String,
    imageRepository: ImageRepository = ImageRepository()
): State<Result<Image>> {
    // Creates a State<T> with Result.Loading as initial value
    // If either `url` or `imageRepository` changes, the running producer
    // will cancel and will be re-launched with the new inputs.
    return produceState<Result<Image>>(initialValue = Result.Loading, url, imageRepository) {
        // In a coroutine, can make suspend calls
        val image = imageRepository.load(url)

        // Update State with either an Error or Success result.
        // This will trigger a recomposition where this State is read
        value = if (image == null) {
            Result.Error
        } else {
            Result.Success(image)
        }
    }
}

derivedStateOf: एक या उससे ज़्यादा स्टेट ऑब्जेक्ट को किसी दूसरी स्टेट में बदलना

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

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

सही इस्तेमाल

नीचे दिए गए स्निपेट में, derivedStateOf के इस्तेमाल का सही उदाहरण दिखाया गया है:

@Composable
// When the messages parameter changes, the MessageList
// composable recomposes. derivedStateOf does not
// affect this recomposition.
fun MessageList(messages: List<Message>) {
    Box {
        val listState = rememberLazyListState()

        LazyColumn(state = listState) {
            // ...
        }

        // Show the button if the first visible item is past
        // the first item. We use a remembered derived state to
        // minimize unnecessary compositions
        val showButton by remember {
            derivedStateOf {
                listState.firstVisibleItemIndex > 0
            }
        }

        AnimatedVisibility(visible = showButton) {
            ScrollToTopButton()
        }
    }
}

इस स्निपेट में, firstVisibleItemIndex की वैल्यू तब बदलती है, जब दिखने वाला पहला आइटम बदलता है. स्क्रोल करने पर, वैल्यू 0, 1, 2, 3, 4, 5 वगैरह हो जाती है. हालांकि, वैल्यू 0 से ज़्यादा होने पर ही, फिर से कंपोज़ करने की ज़रूरत होती है. अपडेट की फ़्रीक्वेंसी में अंतर होने का मतलब है कि यह derivedStateOf के लिए इस्तेमाल का एक अच्छा उदाहरण है.

गलत इस्तेमाल

आम तौर पर, यह गलती हो जाती है कि दो Compose स्टेट ऑब्जेक्ट को एक साथ इस्तेमाल करते समय, derivedStateOf का इस्तेमाल करना चाहिए, क्योंकि "स्टेट को डिराइव किया जा रहा है". हालांकि, यह सिर्फ़ ओवरहेड है और इसकी ज़रूरत नहीं है. इसे यहां दिए गए स्निपेट में दिखाया गया है:

// DO NOT USE. Incorrect usage of derivedStateOf.
var firstName by remember { mutableStateOf("") }
var lastName by remember { mutableStateOf("") }

val fullNameBad by remember { derivedStateOf { "$firstName $lastName" } } // This is bad!!!
val fullNameCorrect = "$firstName $lastName" // This is correct

इस स्निपेट में, fullName को उतनी ही बार अपडेट करना होगा जितनी बार firstName और lastName को अपडेट किया जाता है. इसलिए, कोई अतिरिक्त रीकंपोज़िशन नहीं हो रहा है और derivedStateOf का इस्तेमाल करना ज़रूरी नहीं है.

snapshotFlow: Compose के स्टेट को फ़्लो में बदलें

State<T> ऑब्जेक्ट को कोल्ड फ़्लो में बदलने के लिए, snapshotFlow का इस्तेमाल करें. snapshotFlow ब्लॉक को इकट्ठा करने पर यह चलता है और इसमें पढ़े गए State ऑब्जेक्ट का नतीजा दिखाता है. जब State ब्लॉक में पढ़े गए किसी State ऑब्जेक्ट में बदलाव होता है, तो Flow अपने कलेक्टर को नई वैल्यू देगा. ऐसा तब होगा, जब नई वैल्यू, पहले दी गई वैल्यू के बराबर न हो. यह व्यवहार, Flow.distinctUntilChanged के जैसा होता है.snapshotFlow

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

val listState = rememberLazyListState()

LazyColumn(state = listState) {
    // ...
}

LaunchedEffect(listState) {
    snapshotFlow { listState.firstVisibleItemIndex }
        .map { index -> index > 0 }
        .distinctUntilChanged()
        .filter { it == true }
        .collect {
            MyAnalyticsService.sendScrolledPastFirstItemEvent()
        }
}

ऊपर दिए गए कोड में, listState.firstVisibleItemIndex को एक ऐसे फ़्लो में बदल दिया गया है जो फ़्लो के ऑपरेटरों की सुविधाओं का फ़ायदा ले सकता है.

इफ़ेक्ट रीस्टार्ट हो रहे हैं

Compose में कुछ इफ़ेक्ट होते हैं, जैसे कि LaunchedEffect, produceState या DisposableEffect. ये इफ़ेक्ट, वैरिएबल की संख्या में आर्ग्युमेंट और कुंजियां लेते हैं. इनका इस्तेमाल, चल रहे इफ़ेक्ट को रद्द करने और नई कुंजियों के साथ नया इफ़ेक्ट शुरू करने के लिए किया जाता है.

इन एपीआई के लिए सामान्य फ़ॉर्म यह है:

EffectName(restartIfThisKeyChanges, orThisKey, orThisKey, ...) { block }

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

  • इफ़ेक्ट को ज़रूरत से कम बार रीस्टार्ट करने पर, आपके ऐप्लिकेशन में गड़बड़ियां हो सकती हैं.
  • इफ़ेक्ट को ज़रूरत से ज़्यादा बार रीस्टार्ट करने से, बैटरी की खपत बढ़ सकती है.

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

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

@Composable
fun HomeScreen(
    lifecycleOwner: LifecycleOwner = LocalLifecycleOwner.current,
    onStart: () -> Unit, // Send the 'started' analytics event
    onStop: () -> Unit // Send the 'stopped' analytics event
) {
    // These values never change in Composition
    val currentOnStart by rememberUpdatedState(onStart)
    val currentOnStop by rememberUpdatedState(onStop)

    DisposableEffect(lifecycleOwner) {
        val observer = LifecycleEventObserver { _, event ->
            /* ... */
        }

        lifecycleOwner.lifecycle.addObserver(observer)
        onDispose {
            lifecycleOwner.lifecycle.removeObserver(observer)
        }
    }
}

currentOnStart और currentOnStop को DisposableEffect कुंजियों के तौर पर इस्तेमाल करने की ज़रूरत नहीं है, क्योंकि rememberUpdatedState का इस्तेमाल करने की वजह से, कंपोज़िशन में इनकी वैल्यू कभी नहीं बदलती. अगर आपने lifecycleOwner को पैरामीटर के तौर पर पास नहीं किया है और यह बदल जाता है, तो HomeScreen फिर से कंपोज़ होता है. हालांकि, DisposableEffect को बंद नहीं किया जाता और न ही इसे फिर से शुरू किया जाता है. इससे समस्याएं होती हैं, क्योंकि उस पॉइंट के बाद से गलत lifecycleOwner का इस्तेमाल किया जाता है.

कुंजी के तौर पर कॉन्स्टेंट

true जैसे कॉन्स्टेंट का इस्तेमाल, इफ़ेक्ट की के तौर पर किया जा सकता है. इससे यह कॉल साइट के लाइफ़साइकल को फ़ॉलो करता है. इसके इस्तेमाल के कुछ सही उदाहरण हैं. जैसे, ऊपर दिखाया गया LaunchedEffect उदाहरण. हालांकि, ऐसा करने से पहले, दो बार सोचें और पक्का करें कि आपको यही करना है.