Compose में बुरा असर

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

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

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

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

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 दिखाता है जहां इसे कॉल किया गया है. कॉल से बाहर निकलने पर, स्कोप रद्द हो जाएगा.

पिछले उदाहरण के आधार पर, इस कोड का इस्तेमाल करके, उपयोगकर्ता के Button पर टैप करने पर Snackbar दिखाया जा सकता है:

@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 लैम्ब्डा में हमेशा वह नई वैल्यू मौजूद हो जिससे 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 क्लॉज़ शामिल करना चाहिए. ऐसा न करने पर, IDE में बिल्ड के समय गड़बड़ी का मैसेज दिखता है.

SideEffect: Compose स्टेटस को Compose के अलावा किसी दूसरे कोड में पब्लिश करना

Compose की स्थिति को उन ऑब्जेक्ट के साथ शेयर करने के लिए जिन्हें Compose से मैनेज नहीं किया जाता, SideEffectcomposable का इस्तेमाल करें. 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: Compose स्टेटस में न होने वाले आइटम को Compose स्टेटस में बदलना

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

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

सही इस्तेमाल

यहां दिया गया स्निपेट, 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 ऑब्जेक्ट का नतीजा दिखाता है. जब snapshotFlow ब्लॉक में पढ़े गए State ऑब्जेक्ट में बदलाव होता है, तो अगर नई वैल्यू, उत्सर्जित की गई पिछली वैल्यू से बराबर नहीं होती है, तो फ़्लो अपने कलेक्टर को नई वैल्यू उत्सर्जित करेगा. यह व्यवहार, Flow.distinctUntilChanged के व्यवहार से मिलता-जुलता है.

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

इफ़ेक्ट रीस्टार्ट करना

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

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

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 उदाहरण. हालांकि, ऐसा करने से पहले, अच्छी तरह सोच लें और पक्का करें कि आपको यही चाहिए.