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

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