Trả lại kết quả

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 State bằng cách sử dụng conflateAsState.

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, Boolean hoặ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, ResultEventBus sẽ tự động tạo khoá bằng cách sử dụng biểu diễn toString của KClass củ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:

  1. 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.
  2. 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à ResultEffect bắt đầu lắng nghe kết quả.
  3. Bộ nhận xử lý kết quả: ResultEffect nhậ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.
  4. 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, ResultEffect sẽ 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ể ResultEventBus và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: