Kể từ Navigation 3 1.2.0, bạn có thể trả về kết quả từ các đích đến bằng cách sử dụ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 có thể kết hợp, hãy thêm rememberResultEventBusNavEntryDecorator vào danh sách các đối tượng trang trí được truyền đến NavDisplay. Điều này giúp 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 các kiểu dữ liệu nguyên thuỷ) hoặc khi nhiều đích đến trả về các thực thể khác nhau của cùng một kiểu dữ liệu. - Khoá có nguồn gốc từ kiểu: Khi bạn không chỉ định một khoá rõ ràng,
ResultEventBussẽ tự động tạo một khoá bằng cách sử dụng biểu thứctoStringcủaKClasskiểu kết quả (chẳng hạn nhưContact::class.toString()). Sử dụng khoá có nguồn gốc từ kiểu cho các kiểu dữ liệu riêng biệt, dành riêng cho miền.
Trả về kết quả từ một đích đến
Để giữ cho 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 màn hình. Thay vào đó, hãy hiển thị các lambda gọi lại từ màn hình của bạn. 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à điều hướng quay lại.
Bạn có thể gửi kết quả bằng 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á dẫn xuất kiểu:
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ả
Các đích đến có thể sử dụng kết quả bằng cách dùng các hiệu ứng dựa trên sự kiện hoặc các đối tượng có thể quan sát dựa trên trạng thái.
| API | Hành vi | Các trường hợp sử dụng được đề xuất |
|---|---|---|
ResultEffect |
Được xếp hàng đợi: Xử lý tất cả kết quả được phát ra cho khoá theo thứ tự. | Các sự kiện một lần và hiệu ứng phụ (chẳng hạn như hiển thị thanh thông báo nhanh hoặc chuyển tiếp đến một ViewModel). |
conflateAsState |
Conflated: Loại bỏ kết quả trung gian và chỉ giữ lại kết quả mới nhất dưới dạng Compose State. |
Công cụ sửa đổi trạng thái giao diện người dùng có dung lượng nhỏ (chẳng hạn như thẻ bộ lọc đang hoạt động hoặc chế độ chọn ghi đè). |
Xử lý sự kiện chỉ diễn ra 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ị một thanh thông báo nhanh hoặc chuyển tiếp kết quả đến một 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ự được gửi. Ngoài ra, ResultEffect chạy trong một 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 các kết quả liên kết với một khoá kết quả cụ thể:
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 khoá có nguồn gốc 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: Đí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à đẩy ngăn xếp lui. - Bộ nhận nhập thành phần: Đích đến của bộ nhận sẽ trở thành màn hình đang hoạt động và
ResultEffectbắt đầu lắng nghe kết quả. - Receiver processes results (Receiver xử lý kết quả):
ResultEffectnhận và thực thi phần nội dung hiệu ứng cho từng kết quả được gửi cho khoá đó, xử lý tất cả các kết quả theo thứ tự được gửi. - Receiver rời khỏi thành phần: Khi đích đến của receiver bị đẩy 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ả.
Theo dõi 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 để sửa đổi hoặc lọc trạng thái giao diện người dùng cục bộ một cách trực tiếp 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 các 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 khoá có nguồn gốc 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ó ở bên trong bằng cách sử dụng rememberResultEventBus.
Bạn có thể tạo và nâng cấp một 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 hoặc biểu đồ chèn phần phụ thuộc. - Gửi hoặc quan sát kết quả từ cấu trúc ứ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 đến.
Để nâng ResultEventBus, hãy tạo bằng rememberResultEventBus rồi truyền
đế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 chặn bus chuyển lại các sự kiện trước đó cho những đối tượng quan sát mới khi các đí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ả theo khoá rõ ràng (resultBus.removeResult(resultKey)) hoặc theo khoá có nguồn gốc từ loại (resultBus.removeResult<T>()). Để biết thông tin chi tiết về việc so khớp khoá, hãy xem 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ự