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 yang terjadi satu kali (seperti menampilkan snackbar konfirmasi atau memicu efek samping) menggunakan ResultEffect.
  • Hasil berbasis status: Untuk mengamati hasil terbaru sebagai State Compose menggunakan conflateAsState.

Menyiapkan bus peristiwa hasil

Agar ResultEventBus tersedia untuk tujuan composable, tambahkan rememberResultEventBusNavEntryDecorator ke daftar dekorator yang diteruskan ke NavDisplay. Tindakan ini akan memberikan 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 dengan 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 berbeda dari jenis data yang sama.
  • Kunci turunan 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 turunan jenis untuk jenis data yang berbeda dan khusus domain.

Menampilkan hasil dari tujuan

Agar composable layar dapat digunakan kembali dan diuji, jangan mengakses LocalResultEventBus secara langsung di dalam UI layar Anda. Sebagai gantinya, ekspos lambda callback dari layar Anda. Di entryProvider, tangani callback dengan mengirimkan hasilnya menggunakan LocalResultEventBus.current dan kembali ke halaman sebelumnya.

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 observable berbasis status.

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

Menangani peristiwa satu kali dengan ResultEffect

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

ResultEffect mengelola antrean untuk hasil yang masuk. Jika beberapa hasil dikirim untuk kunci tertentu, ResultEffect akan memproses setiap hasil sesuai urutan pengirimannya. Selain itu, ResultEffect berjalan dalam cakupan coroutine, yang memungkinkan Anda memanggil fungsi penangguhan 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 turunan 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 menavigasi antartujuan, ResultEffect dijalankan melalui urutan siklus proses berikut:

  1. Pengirim memunculkan: Tujuan pengirim mengirimkan hasil menggunakan resultBus.sendResult(resultKey = "pickup_address", address) dan memunculkan data sebelumnya.
  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 sesuai urutan pengirimannya.
  4. Penerima keluar dari komposisi: Saat tujuan penerima dikeluarkan dari data sebelumnya, ResultEffect akan 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 eksplisit:

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

Hoist ResultEventBus

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

Anda dapat membuat dan mengangkat ResultEventBus secara eksplisit saat perlu:

  • Teruskan instance ResultEventBus langsung ke komponen non-composable atau grafik injeksi dependensi.
  • Mengirim atau mengamati hasil dari scaffolding aplikasi tingkat atas (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 observer baru saat tujuan masuk kembali ke 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 berdasarkan kunci eksplisit (resultBus.removeResult(resultKey)) atau berdasarkan kunci turunan jenis (resultBus.removeResult<T>()). Untuk mengetahui detail tentang pencocokan kunci, lihat Kunci hasil.

Resep

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