ผลข้างเคียงคือการเปลี่ยนแปลงสถานะของแอปที่เกิดขึ้นนอก ขอบเขตของฟังก์ชันที่ประกอบกันได้ เนื่องจากวงจรของ Composable และพร็อพเพอร์ตี้ต่างๆ เช่น การประกอบใหม่ที่คาดเดาไม่ได้ การดำเนินการประกอบใหม่ของ Composable ในลำดับที่แตกต่างกัน หรือการประกอบใหม่ที่ทิ้งได้ Composable ควรไม่มีผลข้างเคียง
อย่างไรก็ตาม บางครั้งก็จำเป็นต้องมีผลข้างเคียง เช่น เพื่อทริกเกอร์เหตุการณ์แบบครั้งเดียว เช่น การแสดงแถบแสดงข้อความหรือการไปยังหน้าจออื่นเมื่อมีเงื่อนไขสถานะ ที่แน่นอน ควรเรียกใช้การดำเนินการเหล่านี้จากสภาพแวดล้อมที่ควบคุมได้ซึ่งรับรู้ถึงวงจรของ Composable ในหน้านี้ คุณจะได้เรียนรู้เกี่ยวกับ API ผลข้างเคียงต่างๆ ที่ Jetpack Compose มีให้
กรณีการใช้งานสถานะและเอฟเฟกต์
ตามที่กล่าวไว้ในเอกสารประกอบการคิดใน Compose คอมโพสابلไม่ควรมีผลข้างเคียง เมื่อต้องการทำการเปลี่ยนแปลงสถานะของแอป (ตามที่อธิบายไว้ในเอกสารการจัดการสถานะ) คุณควรใช้ Effect API เพื่อให้ระบบดำเนินการกับผลข้างเคียงเหล่านั้นในลักษณะที่คาดการณ์ได้
เนื่องจากเอฟเฟกต์ต่างๆ ที่เป็นไปได้ใน Compose ทำให้ผู้ใช้ ใช้เอฟเฟกต์มากเกินไปได้ง่าย ตรวจสอบว่างานที่คุณทำในคอมโพเนนต์เหล่านั้นเกี่ยวข้องกับ UI และไม่ทำให้การไหลของข้อมูลแบบทิศทางเดียวหยุดทำงานตามที่อธิบายไว้ในเอกสารประกอบเกี่ยวกับการจัดการสถานะ
LaunchedEffect: เรียกใช้ฟังก์ชันระงับในขอบเขตของ Composable
หากต้องการดำเนินการตลอดช่วงอายุของ Composable และมีความสามารถในการเรียกใช้ฟังก์ชัน
ระงับ ให้ใช้ Composable LaunchedEffect
เมื่อ LaunchedEffect เข้าสู่ Composition จะเปิด
โครูทีนที่มีบล็อกโค้ดที่ส่งผ่านเป็นพารามิเตอร์ ระบบจะยกเลิกโครูทีนหาก 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
เพื่อรอตามระยะเวลาที่กำหนด จากนั้นจะเคลื่อนไหวอัลฟ่า
เป็น 0 และกลับมาอีกครั้งโดยใช้
animateTo
ซึ่งจะทำซ้ำตลอดอายุการใช้งานของ Composable
rememberCoroutineScope: รับขอบเขตที่รับรู้การจัดองค์ประกอบเพื่อเปิดใช้โครูทีนภายนอก Composable
เนื่องจาก LaunchedEffect เป็นฟังก์ชันที่ใช้ร่วมกันได้ จึงใช้ได้เฉพาะภายในฟังก์ชันที่ใช้ร่วมกันได้อื่นๆ
หากต้องการเปิดใช้โครูทีนภายนอก Composable
แต่กำหนดขอบเขตเพื่อให้ระบบยกเลิกโดยอัตโนมัติเมื่อออกจาก
Composition ให้ใช้
rememberCoroutineScope
นอกจากนี้ ให้ใช้ rememberCoroutineScope เมื่อใดก็ตามที่คุณต้องการควบคุมวงจรของ
หนึ่งหรือหลายโครูทีนด้วยตนเอง เช่น ยกเลิกภาพเคลื่อนไหวเมื่อเกิด
เหตุการณ์ของผู้ใช้
rememberCoroutineScope คือฟังก์ชันที่ประกอบกันได้ซึ่งแสดงผล CoroutineScope ที่เชื่อมโยงกับจุดของการคอมโพสที่เรียกใช้
ระบบจะยกเลิกขอบเขตเมื่อการเรียกใช้ออกจาก Composition
จากตัวอย่างก่อนหน้า คุณสามารถใช้โค้ดนี้เพื่อแสดง 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
Lambda มีค่าล่าสุดที่ LandingScreen สร้างขึ้นใหม่onTimeout เสมอ คุณจะต้องห่อด้วยฟังก์ชัน rememberUpdatedState
ควรใช้ State, currentOnTimeout ที่ส่งคืนในโค้ดในเอฟเฟกต์
DisposableEffect: เอฟเฟกต์ที่ต้องมีการล้างข้อมูล
สำหรับผลข้างเคียงที่ต้องล้างข้อมูลหลังจากที่คีย์เปลี่ยนแปลงหรือหาก
Composable ออกจาก Composition ให้ใช้
DisposableEffect
หากคีย์ DisposableEffect เปลี่ยนไป Composable จะต้องทิ้ง (ล้างข้อมูล) เอฟเฟกต์ปัจจุบัน และรีเซ็ตโดยการเรียกใช้เอฟเฟกต์อีกครั้ง
ตัวอย่างเช่น คุณอาจต้องการส่งเหตุการณ์ Analytics โดยอิงตามเหตุการณ์Lifecycle
โดยใช้LifecycleObserver
หากต้องการฟังเหตุการณ์เหล่านั้นใน Compose ให้ใช้ DisposableEffect เพื่อลงทะเบียนและ
ยกเลิกการลงทะเบียน Observer เมื่อจำเป็น
@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 ให้ใช้ Composable
SideEffect
การใช้ SideEffect จะรับประกันว่าเอฟเฟกต์จะทำงานหลังจากการจัดองค์ประกอบใหม่ที่สำเร็จทุกครั้ง
ในทางกลับกัน การ
ใช้เอฟเฟกต์ก่อนที่จะรับประกันการประกอบใหม่ที่สำเร็จนั้นไม่ถูกต้อง ซึ่งเป็น
กรณีที่เขียนเอฟเฟกต์โดยตรงใน Composable
เช่น ไลบรารีการวิเคราะห์อาจช่วยให้คุณแบ่งกลุ่มประชากรผู้ใช้ได้โดยการแนบข้อมูลเมตาที่กำหนดเอง ("พร็อพเพอร์ตี้ผู้ใช้" ในตัวอย่างนี้) กับเหตุการณ์การวิเคราะห์ทั้งหมดในภายหลัง หากต้องการสื่อสารประเภทผู้ใช้ของ
ผู้ใช้ปัจจุบันไปยังไลบรารีการวิเคราะห์ ให้ใช้ 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
เปิดใช้โครูทีนที่กำหนดขอบเขตไว้ใน Composition ซึ่งสามารถส่งค่าไปยัง
State ที่ส่งคืน ใช้เพื่อเปลี่ยนสถานะที่ไม่ใช่ Compose เป็นสถานะ Compose เช่น การนำสถานะที่ขับเคลื่อนด้วยการสมัครใช้บริการภายนอก เช่น Flow, LiveData หรือ RxJava มาไว้ใน Composition
ระบบจะเปิดตัวโปรดิวเซอร์เมื่อ 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: แปลงออบเจ็กต์สถานะอย่างน้อย 1 รายการเป็นสถานะอื่น
ใน Compose recomposition จะเกิดขึ้น ทุกครั้งที่ออบเจ็กต์สถานะที่สังเกตได้หรืออินพุตที่ใช้ร่วมกันได้มีการเปลี่ยนแปลง ออบเจ็กต์สถานะ หรืออินพุตอาจเปลี่ยนแปลงบ่อยกว่าที่ UI จำเป็นต้องอัปเดต ซึ่งทำให้เกิดการจัดองค์ประกอบใหม่ที่ไม่จำเป็น
คุณควรใช้ฟังก์ชัน derivedStateOf
เมื่ออินพุตไปยัง Composable เปลี่ยนแปลงบ่อยกว่าที่คุณต้องการ
ที่จะทำการ Compose ใหม่ กรณีนี้มักเกิดขึ้นเมื่อมีการเปลี่ยนแปลงบางอย่างบ่อยๆ เช่น
ตำแหน่งการเลื่อน แต่ Composable จำเป็นต้องตอบสนองต่อการเปลี่ยนแปลงดังกล่าวเมื่อข้าม
เกณฑ์ที่กำหนดเท่านั้น derivedStateOf จะสร้างออบเจ็กต์สถานะ Compose ใหม่ที่คุณ
สังเกตได้ว่าจะอัปเดตเฉพาะเท่าที่จำเป็นเท่านั้น ด้วยวิธีนี้ จึงทําให้ทำงาน
คล้ายกับตัวดำเนินการ
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
ในข้อมูลโค้ดนี้ fullName ต้องอัปเดตบ่อยเท่ากับ firstName และ
lastName ดังนั้นจึงไม่มีการจัดองค์ประกอบใหม่มากเกินไป และไม่จำเป็นต้องใช้
derivedStateOf
snapshotFlow: แปลงสถานะของ Compose เป็นโฟลว์
ใช้ snapshotFlow
เพื่อแปลงออบเจ็กต์ State<T>
เป็น Flow แบบเย็น snapshotFlow จะเรียกใช้บล็อกเมื่อรวบรวมและส่งผลลัพธ์ของออบเจ็กต์ State ที่อ่านในบล็อก เมื่อStateออบเจ็กต์
ใดออบเจ็กต์หนึ่งที่อ่านภายในบล็อก snapshotFlow มีการเปลี่ยนแปลง Flow จะปล่อยค่าใหม่
ไปยังตัวรวบรวมหากค่าใหม่ไม่เท่ากับ
ค่าที่ปล่อยก่อนหน้า (ลักษณะการทำงานนี้คล้ายกับของ
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 จะแปลงเป็น 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
เนื่องจากค่าของคีย์ดังกล่าวจะไม่เปลี่ยนแปลงใน Composition เนื่องจากมีการใช้ rememberUpdatedState
หากคุณไม่ส่ง lifecycleOwner เป็นพารามิเตอร์และ
มีการเปลี่ยนแปลง HomeScreen จะสร้างใหม่ แต่ DisposableEffect จะไม่ถูกทิ้ง
และรีสตาร์ท ซึ่งจะทำให้เกิดปัญหาเนื่องจากระบบจะใช้ lifecycleOwner ที่ไม่ถูกต้อง
นับจากนั้นเป็นต้นไป
ค่าคงที่เป็นคีย์
คุณสามารถใช้ค่าคงที่ เช่น true เป็นคีย์เอฟเฟกต์เพื่อติดตามวงจรของเว็บไซต์ที่เรียกใช้ มีกรณีการใช้งานที่ถูกต้องสำหรับ
ฟีเจอร์นี้ เช่น LaunchedEffectตัวอย่างที่แสดงด้านบน อย่างไรก็ตาม ก่อนที่จะดำเนินการดังกล่าว
โปรดคิดให้รอบคอบและตรวจสอบว่าคุณต้องการดำเนินการดังกล่าวจริง
แนะนำสำหรับคุณ
- หมายเหตุ: ข้อความลิงก์จะแสดงเมื่อ JavaScript ปิดอยู่
- สถานะและ Jetpack Compose
- Kotlin สำหรับ Jetpack Compose
- การใช้ View ใน Compose