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

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

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

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

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

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

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

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

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

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

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

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

snapshotFlow: แปลงสถานะของ Compose เป็น Flow

ใช้ 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 จะเปลี่ยนเป็นโฟลว์ที่รับประโยชน์จากความสามารถของโอเปอเรเตอร์ของโฟลว์

เอฟเฟกต์ที่เริ่มทำงานอีกครั้ง

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

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

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