Compose-এ Side-effects

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

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

স্টেট ও এফেক্ট ব্যবহারিক ক্ষেত্র

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 lambda সবসময় যাতে LandingScreen রিকম্পোজ করা লেটেস্ট ভ্যালু থাকে তা নিশ্চিত করতে, onTimeout-কে rememberUpdatedState ফাংশনের মধ্যে রাখতে হবে। কোডে রিটার্ন করা State, currentOnTimeout, প্রভাবে ব্যবহার করা উচিত।

DisposableEffect: ক্লিন-আপ করতে হবে এমন এফেক্ট

কী পরিবর্তন হওয়ার পরে পরিষ্কার করতে হবে এমন সাইড এফেক্টের জন্য অথবা কম্পোজ করার উপযুক্ত আইটেম কম্পোজিশন থেকে বেরিয়ে গেলে, DisposableEffect ব্যবহার করুন। DisposableEffect কী পরিবর্তন হলে, কম্পোজ করার উপযুক্ত আইটেমকে তার বর্তমান এফেক্ট ডিসপোজ (এর জন্য পরিষ্কার করতে হবে) করতে হবে এবং এফেক্ট আবার কল করে রিসেট করতে হবে।

যেমন, আপনি Lifecycle ইভেন্টের ভিত্তিতে অ্যানালিটিক্স ইভেন্ট পাঠাতে চাইতে পারেন LifecycleObserver ব্যবহার করে। 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 কোডে প্রকাশ করা

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-এর জন্য একটি ভাল ব্যবহারিক প্রয়োগ।

ভুল ব্যবহার

সাধারণত, দুটি কম্পোজ স্টেট অবজেক্ট একত্রিত করার সময়, 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-এর স্টেটকে Flows-এ কনভার্ট করা

State<T> অবজেক্টকে কোল্ড ফ্লোতে কনভার্ট করতে snapshotFlow ব্যবহার করুন। snapshotFlow সংগ্রহ করা হলে এর ব্লক রান করে এবং এর মধ্যে পড়া State অবজেক্টের ফলাফল এমিট করে। snapshotFlow ব্লকের মধ্যে রিড করা State অবজেক্টের কোনও একটিতে পরিবর্তন হলে, Flow নতুন ভ্যালু এর কালেক্টরে পাঠাবে, যদি নতুন ভ্যালু আগের পাঠানো ভ্যালুর সমান না হয় (এই আচরণটি 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 উপরে দেখানো উদাহরণ। তবে, এটি করার আগে, দুবার ভেবে দেখুন এবং নিশ্চিত করুন যে আপনার এটিই প্রয়োজন।