ผลข้างเคียงใน Compose

ผลข้างเคียงคือการเปลี่ยนแปลงสถานะของแอปที่เกิดขึ้นนอกขอบเขตของฟังก์ชันคอมโพสิเบิล เนื่องจากวงจรและพร็อพเพอร์ตี้ของคอมโพสิเบิล เช่น การจัดเรียงใหม่ที่ไม่คาดคิด การจัดเรียงใหม่ของคอมโพสิเบิลตามลําดับที่ต่างกัน หรือการจัดเรียงใหม่ที่ทิ้งได้ คอมโพสิเบิลจึงควรไม่มีผลข้างเคียง

อย่างไรก็ตาม บางครั้งก็จำเป็นต้องใช้ผลข้างเคียง เช่น เพื่อเรียกเหตุการณ์ที่เกิดขึ้นเพียงครั้งเดียว เช่น การแสดง Snackbar หรือไปยังหน้าจออื่นตามเงื่อนไขสถานะบางอย่าง การดําเนินการเหล่านี้ควรเรียกใช้จากสภาพแวดล้อมที่มีการควบคุมซึ่งทราบวงจรชีวิตของคอมโพสิเบิล ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับ API ที่มีผลข้างเคียงต่างๆ ที่ Jetpack Compose มีให้

กรณีการใช้งานสถานะและผล

ตามที่อธิบายไว้ในเอกสารการคิดใน Compose คอมโพสิเบิลไม่ควรก่อให้เกิดผลข้างเคียง เมื่อต้องการเปลี่ยนแปลงสถานะของแอป (ตามที่อธิบายไว้ในเอกสารเอกสารประกอบเกี่ยวกับการจัดการสถานะ) คุณควรใช้ Effect API เพื่อให้ผลข้างเคียงเหล่านั้นทำงานในลักษณะที่คาดการณ์ได้

เนื่องจากเอฟเฟกต์ต่างๆ ที่เปิดขึ้นในเครื่องมือเขียนมีหลากหลาย จึงอาจทำให้คุณใช้เอฟเฟกต์มากเกินไปได้ ตรวจสอบว่างานที่ทําในไฟล์ดังกล่าวเกี่ยวข้องกับ UI และไม่ได้รบกวนการไหลของข้อมูลแบบทิศทางเดียวตามที่อธิบายไว้ในเอกสารประกอบเกี่ยวกับการจัดการสถานะ

LaunchedEffect: เรียกใช้ฟังก์ชันที่ระงับชั่วคราวในขอบเขตของคอมโพสิเบิล

หากต้องการทำงานตลอดอายุของคอมโพสิเบิลและสามารถเรียกใช้ฟังก์ชันที่ระงับ ให้ใช้คอมโพสิเบิล LaunchedEffect เมื่อ LaunchedEffect เข้าสู่การคอมโพสิชัน ระบบจะเปิดใช้โคโริวทีนที่มีบล็อกโค้ดที่ส่งผ่านเป็นพารามิเตอร์ ระบบจะยกเลิก coroutine หาก LaunchedEffect ออกจากการคอมโพสิชัน หาก LaunchedEffect ได้รับการคอมโพสิชันใหม่ด้วยคีย์อื่น (ดูส่วนการเริ่มผลกระทบใหม่ด้านล่าง) ระบบจะยกเลิก coroutine ที่มีอยู่และเปิดใช้งานฟังก์ชันการระงับใหม่ใน coroutine ใหม่

ตัวอย่างเช่น นี่คือภาพเคลื่อนไหวที่กะพริบค่าอัลฟ่าโดยมีความล่าช้าที่กำหนดค่าได้

// 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 เพื่อรอตามระยะเวลาที่กำหนด จากนั้นจะแสดงภาพเคลื่อนไหวของค่าอัลฟาทีละขั้นจาก 0 กลับไปที่ 0 โดยใช้ animateTo ซึ่งจะทําซ้ำตลอดอายุการใช้งานของคอมโพสิเบิล

rememberCoroutineScope: รับขอบเขตที่รับรู้การคอมโพสิชันเพื่อเปิดใช้ coroutine นอก composable

เนื่องจาก LaunchedEffect เป็นฟังก์ชันคอมโพสิเบิล จึงใช้ได้เฉพาะภายในฟังก์ชันคอมโพสิเบิลอื่นๆ เท่านั้น หากต้องการเปิดใช้ coroutine นอกคอมโพสิเบิล แต่กําหนดขอบเขตไว้เพื่อให้ยกเลิกโดยอัตโนมัติเมื่อออกจากคอมโพสิชัน ให้ใช้ rememberCoroutineScope นอกจากนี้ ให้ใช้ rememberCoroutineScope ทุกครั้งที่คุณต้องควบคุมวงจรชีวิตของ coroutine อย่างน้อย 1 รายการด้วยตนเอง เช่น การยกเลิกภาพเคลื่อนไหวเมื่อมีเหตุการณ์ของผู้ใช้เกิดขึ้น

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 ต้องรวมเข้ากับฟังก์ชัน rememberUpdatedState เพื่อให้แน่ใจว่า onTimeout lambda always มีค่าล่าสุดที่ LandingScreen คอมโพสใหม่แล้ว State, currentOnTimeout ที่แสดงผลในโค้ดควรใช้ในเอฟเฟกต์

DisposableEffect: เอฟเฟกต์ที่ต้องล้าง

สำหรับผลข้างเคียงที่ต้องล้างออกหลังจากคีย์มีการเปลี่ยนแปลง หรือหากคอมโพสิเบิลออกจากการคอมโพสิชัน ให้ใช้ DisposableEffect หากคีย์ DisposableEffect มีการเปลี่ยนแปลง คอมโพสิเบิลจะต้องทิ้ง (ล้างข้อมูล) เอฟเฟกต์ปัจจุบัน และรีเซ็ตโดยการเรียกใช้เอฟเฟกต์อีกครั้ง

ตัวอย่างเช่น คุณอาจต้องการส่งเหตุการณ์ Analytics โดยอิงตามเหตุการณ์ Lifecycle โดยใช้ LifecycleObserver หากต้องการฟังเหตุการณ์เหล่านั้นในคอมโพสิท ให้ใช้ 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: เผยแพร่สถานะการเขียนโค้ดไปยังโค้ดที่ไม่ใช่การเขียนโค้ด

หากต้องการแชร์สถานะการคอมโพสิชันกับออบเจ็กต์ที่ไม่ได้จัดการโดยคอมโพสิชัน ให้ใช้ 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: แปลงสถานะที่ไม่อยู่ในโหมดคอมโพซให้เป็นสถานะคอมโพซ

produceState จะเปิดใช้งาน coroutine ที่มีขอบเขตเป็นองค์ประกอบ ซึ่งสามารถพุชค่าไปยัง State ที่แสดงผล ใช้เพื่อแปลงสถานะที่ไม่อยู่ในคอมโพสิชันให้เป็นสถานะคอมโพสิชัน เช่น การนำสถานะภายนอกที่ขับเคลื่อนโดยการสมัครใช้บริการ เช่น Flow, LiveData หรือ RxJava มาใช้ในคอมโพสิชัน

ระบบจะเปิดใช้งาน Producer เมื่อ produceState เข้าสู่ Composition และจะยกเลิกเมื่อออกจาก Composition State ที่แสดงผลจะรวมค่าต่างๆ เข้าด้วยกัน การตั้งค่าค่าเดียวกันจะไม่ทริกเกอร์การจัดองค์ประกอบใหม่

แม้ว่า produceState จะสร้าง coroutine แต่ก็สามารถใช้ในการสังเกตแหล่งข้อมูลที่ไม่หยุดทำงานได้เช่นกัน หากต้องการยกเลิกการติดตามแหล่งที่มานั้น ให้ใช้ฟังก์ชัน 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: แปลงออบเจ็กต์สถานะอย่างน้อย 1 รายการเป็นสถานะอื่น

ในคอมโพสิท การจัดองค์ประกอบใหม่จะเกิดขึ้นทุกครั้งที่ออบเจ็กต์สถานะที่สังเกตได้หรืออินพุตแบบคอมโพสิทมีการเปลี่ยนแปลง ออบเจ็กต์สถานะหรืออินพุตอาจเปลี่ยนแปลงบ่อยกว่าที่ UI จำเป็นต้องอัปเดตจริง ซึ่งส่งผลให้มีการจัดองค์ประกอบใหม่โดยไม่จำเป็น

คุณควรใช้ฟังก์ชัน derivedStateOf เมื่ออินพุตของคอมโพสิเบิลมีการเปลี่ยนแปลงบ่อยกว่าที่คุณต้องคอมโพสิเบิลใหม่ กรณีนี้มักเกิดขึ้นเมื่อมีการเปลี่ยนแปลงบางอย่างบ่อยครั้ง เช่น ตำแหน่งการเลื่อน แต่คอมโพสิเบิลต้องตอบสนองต่อการเปลี่ยนแปลงเมื่อข้ามเกณฑ์ที่กำหนดเท่านั้น derivedStateOf จะสร้างออบเจ็กต์สถานะการคอมโพสิทใหม่ที่คุณสังเกตได้ ซึ่งจะอัปเดตเฉพาะเท่าที่คุณต้องการ วิธีนี้ทํางานคล้ายกับโอเปอเรเตอร์ distinctUntilChanged() ของ Kotlin Flows

การใช้งานที่ถูกต้อง

ข้อมูลโค้ดต่อไปนี้แสดงกรณีการใช้งานที่เหมาะสมสําหรับ 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 2 รายการ คุณควรใช้ 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

ในสnippet นี้ fullName ต้องอัปเดตบ่อยเท่ากับ firstName และ lastName ดังนั้นจึงไม่มีการคอมโพสใหม่เกินและไม่จำเป็นต้องใช้ derivedStateOf

snapshotFlow: แปลงสถานะของ Compose เป็นโฟลว์

ใช้ snapshotFlow เพื่อแปลงออบเจ็กต์ State<T> เป็นโฟลว์แบบเย็น snapshotFlow จะเรียกใช้บล็อกเมื่อรวบรวมและแสดงผลลัพธ์ของออบเจ็กต์ State ที่อ่านในนั้น เมื่อออบเจ็กต์ State รายการใดรายการหนึ่งซึ่งอ่านภายในบล็อก snapshotFlow มีการกลายพันธุ์ ฟิวล์จะส่งค่าใหม่ไปยังตัวรวบรวมหากค่าใหม่ไม่เท่ากับค่าที่ส่งก่อนหน้านี้ (ลักษณะการทํางานนี้คล้ายกับของ 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 จะใช้อาร์กิวเมนต์และคีย์จํานวนตัวแปร ซึ่งใช้เพื่อยกเลิกเอฟเฟกต์ที่ทำงานอยู่และเริ่มเอฟเฟกต์ใหม่ด้วยคีย์ใหม่

รูปแบบทั่วไปของ 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 คีย์ เนื่องจากค่าของ currentOnStart และ currentOnStop จะไม่เปลี่ยนแปลงในองค์ประกอบเนื่องจากมีการใช้ rememberUpdatedState หากคุณไม่ได้ส่ง lifecycleOwner เป็นพารามิเตอร์และ lifecycleOwner มีการเปลี่ยนแปลง HomeScreen จะคอมโพสใหม่ แต่จะไม่ทิ้ง DisposableEffect และเริ่มใหม่ ซึ่งจะทำให้เกิดปัญหาเนื่องจากมีการใช้ lifecycleOwner ที่ไม่ถูกต้องนับจากจุดนั้นเป็นต้นไป

ค่าคงที่ใช้เป็นคีย์

คุณสามารถใช้ค่าคงที่ เช่น true เป็นคีย์เอฟเฟกต์เพื่อให้เป็นไปตามวงจรชีวิตของเว็บไซต์ที่เรียกใช้ การใช้คำนี้ก็มีกรณีการใช้งานที่ถูกต้อง เช่น ตัวอย่าง LaunchedEffect ที่แสดงด้านบน อย่างไรก็ตาม ก่อนดำเนินการดังกล่าว ให้คิดให้รอบคอบและตรวจสอบว่าคุณต้องการดำเนินการดังกล่าวจริงๆ