Kể từ Navigation 3 1.2.0, bạn có thể trả về kết quả từ các đích đến bằng API ResultEventBus.
ResultEventBus cung cấp hai mô hình giao tiếp:
- Kết quả dựa trên sự kiện: Đối với các sự kiện tạm thời, một lần (chẳng hạn như hiển thị thanh thông báo nhanh xác nhận hoặc kích hoạt hiệu ứng phụ) bằng cách sử dụng
ResultEffect. - Kết quả dựa trên trạng thái: Để quan sát kết quả mới nhất dưới dạng Compose
Statebằng cách sử dụngconflateAsState.
Thiết lập bus sự kiện kết quả
Để cung cấp ResultEventBus cho các đích đến composable, hãy thêm rememberResultEventBusNavEntryDecorator vào danh sách trình trang trí được truyền đến NavDisplay. Thao tác này sẽ cung cấp cho nội dung của mỗi đích đến một thành phần kết hợp cục bộ LocalResultEventBus.
NavDisplay( /* ... */ entryDecorators = listOf( rememberSaveableStateHolderNavEntryDecorator(), rememberResultEventBusNavEntryDecorator() ) )
Khoá kết quả
ResultEventBus xác định và định tuyến từng kết quả bằng một khoá. Người gửi và người nhận so khớp kết quả bằng cách sử dụng cùng một khoá.
Bạn có thể chỉ định khoá kết quả theo hai cách:
- Khoá rõ ràng: Bạn có thể chỉ định một khoá rõ ràng (chẳng hạn như
resultKey = "pickup_address"). Hãy sử dụng khoá rõ ràng khi trả về các loại phổ biến (chẳng hạn nhưString,Booleanhoặc dữ liệu gốc) hoặc khi nhiều đích đến trả về các thực thể khác nhau của cùng một loại dữ liệu. - Khoá bắt nguồn từ kiểu: Khi bạn không chỉ định khoá rõ ràng,
ResultEventBussẽ tự động tạo khoá bằng cách sử dụng biểu diễntoStringcủaKClasscủa kiểu kết quả (chẳng hạn nhưContact::class.toString()). Sử dụng khoá bắt nguồn từ kiểu cho các kiểu dữ liệu riêng biệt, theo miền cụ thể.
Trả về kết quả từ một đích đến
Để đảm bảo các thành phần kết hợp màn hình có thể sử dụng lại và kiểm thử được, đừng truy cập trực tiếp vào LocalResultEventBus bên trong giao diện người dùng của màn hình. Thay vào đó, hãy hiển thị các hàm lambda gọi lại trên màn hình. Trong entryProvider, hãy xử lý lệnh gọi lại bằng cách gửi kết quả bằng LocalResultEventBus.current và quay lại.
Bạn có thể gửi kết quả bằng một khoá kết quả rõ ràng:
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() } ) }
Bạn cũng có thể gửi kết quả bằng khoá bắt nguồn từ loại:
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() } ) }
Nhận kết quả
Đích đến có thể sử dụng kết quả bằng cách dùng hiệu ứng dựa trên sự kiện hoặc đối tượng quan sát dựa trên trạng thái.
| API | Hành vi | Trường hợp sử dụng đề xuất |
|---|---|---|
ResultEffect |
Queued: Xử lý tất cả kết quả được phát ra cho khoá theo thứ tự. | Sự kiện một lần và tác dụng phụ (chẳng hạn như hiển thị thanh thông báo nhanh hoặc chuyển tiếp đến ViewModel). |
conflateAsState |
Conflated: Bỏ kết quả trung gian và chỉ giữ lại kết quả mới nhất dưới dạng Compose State. |
Đối tượng sửa đổi trạng thái giao diện người dùng gọn nhẹ (chẳng hạn như thẻ bộ lọc đang hoạt động hoặc chế độ ghi đè lựa chọn). |
Xử lý sự kiện một lần bằng ResultEffect
Sử dụng ResultEffect khi xử lý các sự kiện một lần, chẳng hạn như kích hoạt số liệu phân tích, hiển thị thanh thông báo nhanh hoặc chuyển tiếp kết quả đến ViewModel.
ResultEffect duy trì một hàng đợi cho các kết quả đến. Nếu nhiều kết quả được gửi cho một khoá nhất định, ResultEffect sẽ xử lý từng kết quả theo thứ tự gửi. Ngoài ra, ResultEffect chạy trong phạm vi coroutine, cho phép bạn gọi trực tiếp các hàm tạm ngưng bên trong phần nội dung hiệu ứng.
Bạn có thể theo dõi kết quả liên kết với một khoá kết quả rõ ràng:
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") } ) }
Bạn cũng có thể theo dõi kết quả bằng cách sử dụng khoá bắt nguồn từ loại:
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 ) }
Khi di chuyển giữa các đích đến, ResultEffect sẽ thực thi thông qua trình tự vòng đời sau:
- Người gửi phát ra: Đích đến của người gửi sẽ gửi kết quả bằng cách sử dụng
resultBus.sendResult(resultKey = "pickup_address", address)và bật ngăn xếp lui. - Receiver enters composition (Bộ nhận vào thành phần kết hợp): Đích đến của bộ nhận trở thành màn hình đang hoạt động và
ResultEffectbắt đầu lắng nghe kết quả. - Bộ nhận xử lý kết quả:
ResultEffectnhận và thực thi phần thân hiệu ứng cho mỗi kết quả được gửi cho khoá đó, xử lý tất cả các lượt phát theo thứ tự được gửi. - Receiver leaves composition (Receiver rời khỏi thành phần): Khi đích đến của receiver được kéo ra khỏi ngăn xếp lui,
ResultEffectsẽ rời khỏi thành phần và ngừng theo dõi kết quả.
Quan sát kết quả mới nhất dưới dạng trạng thái bằng conflateAsState
Khi bạn chỉ cần giá trị kết quả mới nhất để trực tiếp sửa đổi hoặc lọc trạng thái giao diện người dùng cục bộ và muốn Compose tự động kết hợp lại bất cứ khi nào kết quả cập nhật, hãy gọi conflateAsState trên ResultEventBus.
Bạn có thể quan sát kết quả liên kết với một khoá kết quả rõ ràng:
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") } ) }
Bạn cũng có thể quan sát kết quả bằng cách sử dụng khoá bắt nguồn từ loại:
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 ) }
Dây tời ResultEventBus
Theo mặc định, rememberResultEventBusNavEntryDecorator sẽ tạo và ghi nhớ ResultEventBus của riêng nó trong nội bộ bằng cách sử dụng rememberResultEventBus.
Bạn có thể tạo và chuyển ResultEventBus một cách rõ ràng khi cần:
- Truyền trực tiếp thực thể
ResultEventBusvào các thành phần không kết hợp được hoặc biểu đồ chèn phần phụ thuộc. - Gửi hoặc quan sát kết quả từ khung ứng dụng cấp cao nhất (chẳng hạn như thanh ứng dụng hoặc ngăn điều hướng) bên ngoài hệ phân cấp đích.
Để kéo ResultEventBus lên, hãy tạo trạng thái này bằng rememberResultEventBus và truyền trạng thái đó đến 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 ) )
Quản lý và xoá kết quả
Khi một đích đến sử dụng kết quả một lần, hãy xoá kết quả đó khỏi bus sự kiện bằng cách sử dụng removeResult. Điều này ngăn bus phân phối lại các sự kiện trước đây cho trình quan sát mới khi đích đến nhập lại thành phần:
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") } }
Bạn có thể xoá kết quả bằng khoá rõ ràng (resultBus.removeResult(resultKey)) hoặc bằng khoá bắt nguồn từ loại (resultBus.removeResult<T>()). Để biết thông tin chi tiết về việc so khớp khoá, hãy xem phần Khoá kết quả.
Công thức
Để xem các ví dụ hoàn chỉnh về mã có thể chạy minh hoạ các chiến lược truyền kết quả khác nhau, hãy xem các công thức sau:
- Công thức kết quả dựa trên sự kiện
- Công thức kết quả dựa trên trạng thái
- Công thức kết quả có thể chuyển đổi tuần tự