ตั้งแต่ Navigation 3 1.2.0 เป็นต้นไป คุณสามารถแสดงผลลัพธ์จากปลายทางโดยใช้ API ResultEventBus ได้
ResultEventBus มีรูปแบบการสื่อสาร 2 รูปแบบ ดังนี้
- ผลลัพธ์ตามเหตุการณ์: สำหรับเหตุการณ์ชั่วคราวที่เกิดขึ้นครั้งเดียว (เช่น การแสดงแถบแสดงสถานะการยืนยันหรือการทริกเกอร์ผลข้างเคียง) โดยใช้
ResultEffect - ผลลัพธ์ตามสถานะ: สำหรับการสังเกตผลลัพธ์ล่าสุดเมื่อ Compose
Stateโดยใช้conflateAsState
ตั้งค่าบัสเหตุการณ์ผลลัพธ์
หากต้องการให้ ResultEventBus พร้อมใช้งานสำหรับปลายทาง Composable ให้เพิ่ม rememberResultEventBusNavEntryDecorator ลงในรายการเดคอเรเตอร์ที่ส่งไปยัง NavDisplay ซึ่งจะทำให้เนื้อหาของแต่ละปลายทางมี LocalResultEventBus องค์ประกอบในเครื่อง
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
คีย์ผลลัพธ์
ResultEventBus จะระบุและกำหนดเส้นทางผลลัพธ์แต่ละรายการโดยใช้คีย์ ผู้ส่งและผู้รับจะจับคู่ผลลัพธ์โดยใช้คีย์เดียวกัน
คุณระบุคีย์ผลลัพธ์ได้ 2 วิธีดังนี้
- คีย์ที่ชัดเจน: คุณสามารถระบุคีย์ที่ชัดเจน (เช่น
resultKey = "pickup_address") ใช้คีย์ที่ชัดเจนเมื่อแสดงผลประเภททั่วไป (เช่นString,Booleanหรือพื้นฐาน) หรือเมื่อปลายทางหลายแห่งแสดงผลอินสแตนซ์ที่แตกต่างกันของประเภทข้อมูลเดียวกัน - คีย์ที่ได้มาจากประเภท: เมื่อคุณไม่ได้ระบุคีย์ที่ชัดเจน
ResultEventBusจะสร้างคีย์โดยอัตโนมัติโดยใช้การแสดงผลtoStringของKClassของประเภทผลลัพธ์ (เช่นContact::class.toString()) ใช้คีย์ที่ได้มาจากประเภทสำหรับประเภทข้อมูลที่เฉพาะเจาะจงโดเมนที่แตกต่างกัน
แสดงผลการค้นหาจากปลายทาง
อย่าเข้าถึง LocalResultEventBus โดยตรงภายใน UI ของหน้าจอ เพื่อให้หน้าจอที่ประกอบกันได้สามารถนำกลับมาใช้ซ้ำและทดสอบได้ แต่ให้แสดง Callback Lambda จากหน้าจอแทน ใน entryProvider ให้จัดการการเรียกกลับโดยส่งผลลัพธ์โดยใช้ LocalResultEventBus.current และกลับไปที่หน้าเดิม
คุณสามารถส่งผลลัพธ์โดยใช้คีย์ผลลัพธ์ที่ชัดเจนได้ดังนี้
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.LocalResultEventBus entry<AddressPickerRoute> { val resultBus = LocalResultEventBus.current AddressPickerScreen( onAddressSelected = { selectedAddress: Address -> resultBus.sendResult( resultKey = "pickup_address", result = selectedAddress ) navigator.goBack() } ) }
นอกจากนี้ คุณยังส่งผลลัพธ์โดยใช้คีย์ที่ได้มาจากประเภทได้ด้วย ดังนี้
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.LocalResultEventBus entry<ContactPickerRoute> { val resultBus = LocalResultEventBus.current ContactPickerScreen( onContactSelected = { selectedContact: Contact -> resultBus.sendResult(result = selectedContact) navigator.goBack() } ) }
รับผลลัพธ์
ปลายทางสามารถใช้ผลลัพธ์โดยใช้เอฟเฟกต์ตามเหตุการณ์หรือออบเจ็กต์ที่สังเกตได้ตามสถานะ
| API | ลักษณะการทำงาน | กรณีการใช้งานที่แนะนำ |
|---|---|---|
ResultEffect |
Queued: ประมวลผลผลลัพธ์ทั้งหมดที่ส่งสำหรับคีย์ตามลำดับ | เหตุการณ์ที่เกิดขึ้นครั้งเดียวและผลข้างเคียง (เช่น การแสดงแถบแสดงสถานะหรือการส่งต่อไปยัง ViewModel) |
conflateAsState |
Conflated: ละทิ้งผลลัพธ์ระดับกลางและเก็บเฉพาะผลลัพธ์ล่าสุดเป็น Compose State |
ตัวแก้ไขสถานะ UI แบบ Lightweight (เช่น แท็กตัวกรองที่ใช้งานอยู่หรือการลบล้างการเลือก) |
จัดการเหตุการณ์ที่เกิดขึ้นครั้งเดียวด้วย ResultEffect
ใช้ ResultEffect เมื่อจัดการเหตุการณ์ที่เกิดขึ้นครั้งเดียว เช่น การทริกเกอร์การวิเคราะห์ การแสดงแถบแสดงข้อความสั้นๆ หรือการส่งต่อผลลัพธ์ไปยัง ViewModel
ResultEffect จะรักษาคิวสำหรับผลลัพธ์ที่เข้ามา หากมีการส่งผลลัพธ์หลายรายการสำหรับคีย์หนึ่งๆ ResultEffect จะประมวลผลผลลัพธ์แต่ละรายการตามลำดับที่ส่ง นอกจากนี้ ResultEffect ยังทำงานในขอบเขต Coroutine ซึ่งช่วยให้คุณเรียกใช้ฟังก์ชันการระงับได้โดยตรงภายในเนื้อหาเอฟเฟกต์
คุณสามารถรอฟังผลลัพธ์ที่เชื่อมโยงกับคีย์ผลลัพธ์ที่ชัดเจนได้ดังนี้
import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.ResultEffect @Composable fun RideSummaryScreen( onOpenAddressPicker: (key: String) -> Unit, viewModel: RideSummaryViewModel = viewModel() ) { ResultEffect<Address>(resultKey = "pickup_address") { address -> viewModel.onPickupAddressSelected(address) } ResultEffect<Address>(resultKey = "destination_address") { address -> viewModel.onDestinationAddressSelected(address) } RideSummaryContent( pickupAddress = viewModel.pickupAddress, destinationAddress = viewModel.destinationAddress, onPickPickup = { onOpenAddressPicker("pickup_address") }, onPickDestination = { onOpenAddressPicker("destination_address") } ) }
นอกจากนี้ คุณยังฟังผลลัพธ์โดยใช้คีย์ที่ได้มาจากประเภทได้ด้วย ดังนี้
import androidx.compose.material3.SnackbarHostState import androidx.compose.runtime.Composable import androidx.compose.runtime.remember import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.ResultEffect @Composable fun ComposeMessageScreen( onPickContact: () -> Unit, snackbarHostState: SnackbarHostState = remember { SnackbarHostState() }, viewModel: ComposeMessageViewModel = viewModel() ) { ResultEffect<Contact> { contact -> // Suspending calls are supported directly in the effect body snackbarHostState.showSnackbar("Selected ${contact.name}") viewModel.onRecipientSelected(contact) } ComposeMessageContent( recipient = viewModel.recipient, onPickContact = onPickContact ) }
เมื่อไปยังส่วนต่างๆ ระหว่างปลายทาง ResultEffect จะดำเนินการตามลำดับวงจรต่อไปนี้
- Sender emits: ปลายทางของผู้ส่งจะส่งผลลัพธ์โดยใช้
resultBus.sendResult(resultKey = "pickup_address", address)และแสดง Back Stack - Receiver เข้าสู่ Composition: ปลายทาง Receiver จะกลายเป็นหน้าจอที่ใช้งานอยู่ และ
ResultEffectจะเริ่มฟังผลลัพธ์ - Receiver processes results:
ResultEffectreceives and executes its effect body for each result sent for that key, processing all emissions in the order they were sent. - Receiver ออกจาก Composition: เมื่อปลายทางของ Receiver ถูกนำออกจาก Back Stack แล้ว
ResultEffectจะออกจาก Composition และหยุดรอผลลัพธ์
สังเกตผลลัพธ์ล่าสุดเป็นสถานะด้วย conflateAsState
เมื่อคุณต้องการเพียงค่าผลลัพธ์ล่าสุดเพื่อแก้ไขหรือกรองสถานะ UI ในเครื่องโดยตรง และต้องการให้ Compose ทำการคอมโพสใหม่โดยอัตโนมัติเมื่อใดก็ตามที่ผลลัพธ์อัปเดต ให้เรียกใช้ conflateAsState ใน ResultEventBus
คุณสามารถดูผลลัพธ์ที่เชื่อมโยงกับคีย์ผลลัพธ์ที่ชัดเจนได้ดังนี้
import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.ui.graphics.Color import androidx.navigation3.runtime.result.LocalResultEventBus @Composable fun ThemePreviewScreen( onOpenColorPicker: (key: String) -> Unit ) { val resultBus = LocalResultEventBus.current val primaryColor by resultBus.conflateAsState<Color>( resultKey = "primary_color", defaultValue = MaterialTheme.colorScheme.primary ) val accentColor by resultBus.conflateAsState<Color>( resultKey = "accent_color", defaultValue = MaterialTheme.colorScheme.tertiary ) ThemePreviewContent( primaryColor = primaryColor, accentColor = accentColor, onPickPrimary = { onOpenColorPicker("primary_color") }, onPickAccent = { onOpenColorPicker("accent_color") } ) }
นอกจากนี้ คุณยังสังเกตผลลัพธ์โดยใช้คีย์ที่ได้มาจากประเภทได้ด้วย ดังนี้
import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.navigation3.runtime.result.LocalResultEventBus @Composable fun FilterableProductListScreen( initialFilter: ProductFilter = ProductFilter.All, onOpenFilterPicker: () -> Unit ) { val resultBus = LocalResultEventBus.current // Observe latest filter result as Compose State, starting with initialFilter val activeFilter by resultBus.conflateAsState<ProductFilter>( defaultValue = initialFilter ) ProductListContent( activeFilter = activeFilter, onOpenFilterPicker = onOpenFilterPicker ) }
เครื่องยก ResultEventBus
โดยค่าเริ่มต้น rememberResultEventBusNavEntryDecorator จะสร้างและจดจำ ResultEventBus ของตัวเองภายในโดยใช้ rememberResultEventBus
คุณสามารถสร้างและยก ResultEventBus ได้อย่างชัดเจนเมื่อต้องการทำสิ่งต่อไปนี้
- ส่งอินสแตนซ์
ResultEventBusไปยังคอมโพเนนต์ที่ประกอบไม่ได้หรือกราฟการแทรกทรัพยากร Dependency โดยตรง - ส่งหรือสังเกตผลลัพธ์จากโครงสร้างแอปที่ระดับบนสุด (เช่น แถบแอปหรือลิ้นชักการนำทาง) นอกลำดับชั้นปลายทาง
หากต้องการยก ResultEventBus ให้สร้างโดยใช้ rememberResultEventBus และส่งไปยัง rememberResultEventBusNavEntryDecorator(resultEventBus) ดังนี้
import androidx.compose.runtime.Composable import androidx.navigation3.runtime.result.rememberResultEventBus import androidx.navigation3.runtime.result.rememberResultEventBusNavEntryDecorator import androidx.navigation3.ui.NavDisplay // Hoist the ResultEventBus at the top level val resultEventBus = rememberResultEventBus() // Pass the hoisted bus to the decorator val resultEventBusNavEntryDecorator = rememberResultEventBusNavEntryDecorator<NavKey>( resultEventBus = resultEventBus ) NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), resultEventBusNavEntryDecorator ) )
จัดการและล้างผลการค้นหา
เมื่อปลายทางใช้ผลลัพธ์แบบครั้งเดียว ให้ล้างผลลัพธ์ออกจากบัสเหตุการณ์โดยใช้ removeResult ซึ่งจะป้องกันไม่ให้บัสส่งเหตุการณ์ที่ผ่านมาไปยังผู้สังเกตการณ์รายใหม่อีกครั้งเมื่อปลายทางเข้าสู่ Composition อีกครั้ง
import androidx.compose.runtime.Composable import androidx.lifecycle.viewmodel.compose.viewModel import androidx.navigation3.runtime.result.LocalResultEventBus import androidx.navigation3.runtime.result.ResultEffect @Composable fun NotificationSettingsScreen( viewModel: NotificationViewModel = viewModel() ) { val resultBus = LocalResultEventBus.current ResultEffect<ConfirmationResult>(resultKey = "confirm_permission") { confirmation -> viewModel.onPermissionConfirmed(confirmation) // Clear the result after consumption to prevent re-delivery resultBus.removeResult(resultKey = "confirm_permission") } }
คุณสามารถล้างผลลัพธ์ตามคีย์ที่ชัดเจน (resultBus.removeResult(resultKey)) หรือตามคีย์ที่ได้มาจากประเภท (resultBus.removeResult<T>()) ได้ โปรดดูรายละเอียดเกี่ยวกับการจับคู่คีย์ที่หัวข้อคีย์ผลลัพธ์
สูตรอาหาร
ดูตัวอย่างโค้ดที่รันได้แบบสมบูรณ์ซึ่งแสดงกลยุทธ์การส่งผลลัพธ์แบบต่างๆ ได้ในสูตรต่อไปนี้