Tampilkan hasil

Mulai Navigation 3 1.2.0, Anda dapat menampilkan hasil dari tujuan menggunakan API ResultEventBus.

ResultEventBus menyediakan dua model komunikasi:

  • Hasil berbasis peristiwa: Untuk peristiwa sementara dan satu kali (seperti menampilkan snackbar konfirmasi atau memicu efek samping) menggunakan ResultEffect.
  • Hasil berbasis status: Untuk mengamati hasil terbaru sebagai Compose State menggunakan conflateAsState.

Menyiapkan bus peristiwa hasil

Untuk membuat ResultEventBus tersedia bagi tujuan composable Anda, tambahkan rememberResultEventBusNavEntryDecorator ke daftar decorator yang diteruskan ke NavDisplay Anda. Hal ini menyediakan konten setiap tujuan dengan lokal komposisi LocalResultEventBus.

NavDisplay(
    /* ... */
    entryDecorators = listOf(
        rememberSaveableStateHolderNavEntryDecorator(),
        rememberResultEventBusNavEntryDecorator()
    )
)

Kunci hasil

ResultEventBus mengidentifikasi dan merutekan setiap hasil menggunakan kunci. Pengirim dan penerima mencocokkan hasil menggunakan kunci yang sama.

Anda dapat menentukan kunci hasil dengan dua cara:

  • Kunci eksplisit: Anda dapat menentukan kunci eksplisit (seperti resultKey = "pickup_address"). Gunakan kunci eksplisit saat menampilkan jenis umum (seperti String, Boolean, atau primitif), atau saat beberapa tujuan menampilkan instance jenis data yang sama yang berbeda.
  • Kunci yang diturunkan dari jenis: Jika Anda tidak menentukan kunci eksplisit, ResultEventBus akan otomatis membuat kunci menggunakan representasi toString dari KClass jenis hasil (seperti Contact::class.toString()). Gunakan kunci yang diturunkan dari jenis untuk jenis data khusus domain yang berbeda.

Menampilkan hasil dari tujuan

Agar composable layar dapat digunakan kembali dan diuji, jangan akses LocalResultEventBus secara langsung di dalam UI layar Anda. Sebagai gantinya, ekspos lambda callback dari layar Anda. Di entryProvider, tangani callback dengan mengirim hasil menggunakan LocalResultEventBus.current dan kembali.

Anda dapat mengirim hasil menggunakan kunci hasil eksplisit:

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()
        }
    )
}

Anda juga dapat mengirim hasil menggunakan kunci turunan jenis:

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()
        }
    )
}

Menerima hasil

Tujuan dapat menggunakan hasil menggunakan efek berbasis peristiwa atau dapat diamati berbasis status.

API Perilaku Kasus penggunaan yang direkomendasikan
ResultEffect Dalam antrean: Memproses semua hasil yang dikeluarkan untuk kunci secara berurutan. Peristiwa dan efek samping satu kali (seperti menampilkan snackbar atau meneruskan ke ViewModel).
conflateAsState Conflated: Menghapus hasil sementara dan hanya mempertahankan hasil terbaru sebagai Compose State. Pengubah status UI ringan (seperti tag filter aktif atau penggantian pilihan).

Menangani acara satu kali dengan ResultEffect

Gunakan ResultEffect saat menangani peristiwa satu kali seperti memicu analisis, menampilkan snackbar, atau meneruskan hasil ke ViewModel.

ResultEffect mempertahankan antrean untuk hasil yang masuk. Jika beberapa hasil dikirim untuk kunci tertentu, ResultEffect akan memproses setiap hasil dalam urutan pengirimannya. Selain itu, ResultEffect berjalan dalam cakupan coroutine, yang memungkinkan Anda memanggil fungsi penangguhan secara langsung di dalam isi efek.

Anda dapat memproses hasil yang terkait dengan kunci hasil eksplisit:

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") }
    )
}

Anda juga dapat memproses hasil menggunakan kunci yang berasal dari jenis:

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
    )
}

Saat melakukan navigasi antar-tujuan, ResultEffect dieksekusi melalui urutan siklus proses berikut:

  1. Pengirim memancarkan: Tujuan pengirim mengirimkan hasil menggunakan resultBus.sendResult(resultKey = "pickup_address", address) dan memunculkan back stack.
  2. Penerima memasuki komposisi: Tujuan penerima menjadi layar aktif, dan ResultEffect mulai memproses hasil.
  3. Penerima memproses hasil: ResultEffect menerima dan mengeksekusi isi efeknya untuk setiap hasil yang dikirim untuk kunci tersebut, memproses semua emisi dalam urutan pengirimannya.
  4. Penerima keluar dari komposisi: Saat tujuan penerima dikeluarkan dari entri data sebelumnya, ResultEffect keluar dari komposisi dan berhenti memproses hasil.

Mengamati hasil terbaru sebagai status dengan conflateAsState

Jika Anda hanya memerlukan nilai hasil terbaru untuk langsung mengubah atau memfilter status UI lokal dan ingin Compose otomatis merekomposisi setiap kali hasil diperbarui, panggil conflateAsState di ResultEventBus.

Anda dapat mengamati hasil yang terkait dengan kunci hasil vulgar:

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") }
    )
}

Anda juga dapat mengamati hasil menggunakan kunci yang berasal dari jenis:

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
    )
}

Kerekan ResultEventBus

Secara default, rememberResultEventBusNavEntryDecorator membuat dan mengingat ResultEventBus-nya sendiri secara internal menggunakan rememberResultEventBus.

Anda dapat membuat dan mengangkat ResultEventBus secara eksplisit saat Anda perlu:

  • Teruskan instance ResultEventBus langsung ke komponen non-composable atau grafik injeksi dependensi.
  • Mengirim atau mengamati hasil dari scaffolding aplikasi tingkat teratas (seperti panel aplikasi atau panel navigasi) di luar hierarki tujuan.

Untuk mengangkat ResultEventBus, buat menggunakan rememberResultEventBus dan teruskan ke 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
    )
)

Mengelola dan menghapus hasil

Saat tujuan menggunakan hasil satu kali, hapus hasil tersebut dari bus peristiwa menggunakan removeResult. Hal ini mencegah bus mengirim ulang peristiwa sebelumnya ke pengamat baru saat tujuan memasuki kembali komposisi:

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")
    }
}

Anda dapat menghapus hasil menurut kunci eksplisit (resultBus.removeResult(resultKey)) atau menurut kunci yang berasal dari jenis (resultBus.removeResult<T>()). Untuk mengetahui detail tentang pencocokan kunci, lihat Kunci hasil.

Resep

Untuk contoh kode yang dapat dijalankan lengkap yang menunjukkan berbagai strategi penerusan hasil, lihat resep berikut: