Compose-এ Side-effects

সাইড-এফেক্ট হল অ্যাপের স্ট্যাটাসে হওয়া পরিবর্তন যা কম্পোজেবল ফাংশনের স্কোপের বাইরে ঘটে। কম্পোজেবলের লাইফসাইকেল এবং অপ্রত্যাশিত রিকম্পোজিশন, বিভিন্ন ক্রমে কম্পোজেবলের রিকম্পোজিশন কার্যকর করা বা বাতিল করা যেতে পারে এমন রিকম্পোজিশনের মতো প্রপার্টির কারণে, কম্পোজেবল আদর্শগতভাবে সাইড-ইফেক্ট মুক্ত হওয়া উচিত।

তবে, কখনও কখনও সাইড-এফেক্ট প্রয়োজন হয়, যেমন, কোনও নির্দিষ্ট স্টেট কন্ডিশনের ভিত্তিতে স্ন্যাকবার দেখানো বা অন্য স্ক্রিনে নেভিগেট করার মতো ওয়ান-অফ ইভেন্ট ট্রিগার করা। এইসব অ্যাকশন এমন একটি নিয়ন্ত্রিত পরিবেশ থেকে কল করা উচিত যা কম্পোজেবলের লাইফসাইকেল সম্পর্কে জানে। এই পৃষ্ঠায়, আপনি Jetpack Compose-এর অফার করা বিভিন্ন সাইড-এফেক্ট API সম্পর্কে জানতে পারবেন।

স্টেট ও এফেক্ট ব্যবহারের উদাহরণ

Thinking in Compose ডকুমেন্টেশনে যেমন বলা হয়েছে, কম্পোজেবল সাইড-এফেক্ট মুক্ত হওয়া উচিত। অ্যাপের স্ট্যাটাসে পরিবর্তন করতে হলে (স্ট্যাটাস ডকুমেন্টেশন ম্যানেজ করা ডকুমেন্টে যেমন বর্ণনা করা হয়েছে), আপনাকে Effect API ব্যবহার করতে হবে যাতে সেইসব সাইড এফেক্ট অনুমানযোগ্য পদ্ধতিতে কার্যকর করা যায়।

Compose-এ এফেক্ট ব্যবহারের বিভিন্ন সম্ভাবনা থাকার কারণে, সেগুলি সহজেই অতিরিক্ত ব্যবহার করা হতে পারে। আপনি যে কাজ করছেন তা UI সম্পর্কিত কিনা এবং স্টেট ম্যানেজ করা সংক্রান্ত ডকুমেন্টেশনে যেভাবে ব্যাখ্যা করা হয়েছে সেই অনুযায়ী ইউনিডিরেকশনাল ডেটা ফ্লো ব্রেক করছে না কিনা তা ভালভাবে দেখে নিন।

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 { mutableLongStateOf(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 ইভেন্টের উপর ভিত্তি করে অ্যানালিটিক্স ইভেন্ট পাঠাতে চাইবেন। 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 */
}

উপরের কোডে, এফেক্টটি observer-কে lifecycleOwner-এ যোগ করবে। lifecycleOwner পরিবর্তন হলে, এফেক্টটি বাতিল করা হয় এবং নতুন lifecycleOwner-এর সাথে রিস্টার্ট করা হয়।

DisposableEffect-এ অবশ্যই কোডের ব্লকে চূড়ান্ত স্টেটমেন্ট হিসেবে onDispose ক্লজ থাকতে হবে। অন্যথায়, IDE-তে বিল্ড-টাইম সংক্রান্ত সমস্যা দেখা যায়।

SideEffect: কম্পোজ স্টেটকে নন-কম্পোজ কোডে প্রকাশ করুন

Compose-এর মাধ্যমে ম্যানেজ করা হয় না এমন অবজেক্টের সাথে Compose স্টেট শেয়ার করতে, SideEffect কম্পোজেবল ব্যবহার করুন। SideEffect ব্যবহার করলে গ্যারেন্টি পাওয়া যায় যে প্রতিটি সফল রিকম্পোজিশনের পরে এফেক্টটি এক্সিকিউট হবে। অন্যদিকে, সফল রিকম্পোজিশন নিশ্চিত করার আগে কোনও এফেক্ট পারফর্ম করা ভুল, যা সরাসরি কম্পোজেবলে এফেক্ট লেখার ক্ষেত্রে ঘটে।

যেমন, আপনার অ্যানালিটিক্স লাইব্রেরি আপনাকে পরবর্তী সব অ্যানালিটিক্স ইভেন্টে কাস্টম মেটাডেটা (এই উদাহরণে "ব্যবহারকারীর প্রপার্টি") যোগ করার মাধ্যমে ব্যবহারকারীর জনসংখ্যাকে ভাগ করার অনুমতি দিতে পারে। আপনার অ্যানালিটিক্স লাইব্রেরিতে বর্তমান ব্যবহারকারীর ধরন জানাতে, 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-এ, প্রতিবার লক্ষ্য করা স্টেট অবজেক্ট বা কম্পোজেবল ইনপুট পরিবর্তন হলে রিকম্পোজিশন হয়। UI-এর আপডেট করার প্রয়োজনের তুলনায় স্টেট অবজেক্ট বা ইনপুট আরও ঘন ঘন পরিবর্তন হতে পারে, যার ফলে অপ্রয়োজনীয় রিকম্পোজিশন হতে পারে।

আপনার কম্পোজেবল ইনপুটগুলি আপনার রিকম্পোজ করার প্রয়োজনের চেয়ে বেশি ঘন ঘন পরিবর্তিত হলে আপনার derivedStateOf ফাংশনটি ব্যবহার করা উচিত। এটি প্রায়শই ঘটে যখন কোনও কিছু ঘন ঘন পরিবর্তিত হয়, যেমন স্ক্রল পজিশন, কিন্তু কম্পোজেবলকে শুধুমাত্র তখনই প্রতিক্রিয়া জানাতে হয় যখন এটি একটি নির্দিষ্ট থ্রেশহোল্ড অতিক্রম করে। derivedStateOf একটি নতুন কম্পোজ স্টেট অবজেক্ট তৈরি করে যা আপনি দেখতে পারবেন এবং এটি শুধুমাত্র আপনার প্রয়োজন অনুযায়ী আপডেট হয়। এইভাবে, এটি Kotlin Flow 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 অবজেক্টের ফলাফল প্রকাশ করে। snapshotFlow ব্লকের মধ্যে পড়া State অবজেক্টের মধ্যে কোনও একটি মিউটেট করলে, নতুন ভ্যালু আগের নির্গত ভ্যালুর সমান না হলে, ফ্লো তার কালেক্টরে নতুন ভ্যালু নির্গত করবে (এই আচরণ Flow.distinctUntilChanged-এর মতো)।

নিম্নলিখিত উদাহরণে এমন একটি সাইড এফেক্ট দেখানো হয়েছে যা ব্যবহারকারী তালিকার প্রথম আইটেমটি স্ক্রল করে পেরিয়ে গেলে অ্যানালিটিক্সে রেকর্ড করে:

val listState = rememberLazyListState()

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

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

উপরের কোডে, listState.firstVisibleItemIndex-কে Flow-তে কনভার্ট করা হয়েছে যা Flow-এর অপারেটরের ক্ষমতা থেকে সুবিধা পেতে পারে।

এফেক্ট রিস্টার্ট করা হচ্ছে

Compose-এ কিছু এফেক্ট, যেমন LaunchedEffect, produceState, বা DisposableEffect, ভেরিয়েবল সংখ্যক আর্গুমেন্ট, কী নেয়, যা চলমান এফেক্ট বাতিল করতে এবং নতুন কী সহ নতুন একটি শুরু করতে ব্যবহৃত হয়।

এইসব API-এর সাধারণ ফর্ম হল:

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-এর উদাহরণ। তবে, এটি করার আগে, ভাল করে ভাবুন এবং নিশ্চিত করুন যে আপনার এটিই প্রয়োজন।