ส่งคืนผลลัพธ์

ตั้งแต่ Navigation 3 1.2.0 เป็นต้นไป คุณสามารถแสดงผลลัพธ์จากปลายทางโดยใช้ API ResultEventBus ได้

ResultEventBus มีรูปแบบการสื่อสาร 2 รูปแบบ ดังนี้

  • ผลลัพธ์ตามเหตุการณ์: สำหรับเหตุการณ์ชั่วคราวที่เกิดขึ้นครั้งเดียว (เช่น การแสดงแถบแสดงสถานะการยืนยันหรือการทริกเกอร์ผลข้างเคียง) โดยใช้ ResultEffect
  • ผลลัพธ์ตามสถานะ: สำหรับการสังเกตผลลัพธ์ล่าสุดเมื่อ Compose State โดยใช้ conflateAsState

ตั้งค่าบัสเหตุการณ์ผลลัพธ์

หากต้องการให้ ResultEventBus พร้อมใช้งานสำหรับปลายทาง Composable ให้เพิ่ม rememberResultEventBusNavEntryDecorator ลงในรายการเดคอเรเตอร์ที่ส่งไปยัง NavDisplay ซึ่งจะทำให้เนื้อหาของแต่ละปลายทางมี LocalResultEventBus องค์ประกอบในเครื่อง

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

คีย์ผลลัพธ์

ResultEventBus จะระบุและกำหนดเส้นทางผลลัพธ์แต่ละรายการโดยใช้คีย์ ผู้ส่งและผู้รับจะจับคู่ผลลัพธ์โดยใช้คีย์เดียวกัน

คุณระบุคีย์ผลลัพธ์ได้ 2 วิธีดังนี้

  • คีย์ที่ชัดเจน: คุณสามารถระบุคีย์ที่ชัดเจน (เช่น resultKey = "pickup_address") ใช้คีย์ที่ชัดเจนเมื่อแสดงผลประเภททั่วไป (เช่น String, Boolean หรือพื้นฐาน) หรือเมื่อปลายทางหลายแห่งแสดงผลอินสแตนซ์ที่แตกต่างกันของประเภทข้อมูลเดียวกัน
  • คีย์ที่ได้มาจากประเภท: เมื่อคุณไม่ได้ระบุคีย์ที่ชัดเจน ResultEventBus จะสร้างคีย์โดยอัตโนมัติโดยใช้การแสดงผล toString ของ KClass ของประเภทผลลัพธ์ (เช่น Contact::class.toString()) ใช้คีย์ที่ได้มาจากประเภทสำหรับประเภทข้อมูลที่เฉพาะเจาะจงโดเมนที่แตกต่างกัน

แสดงผลการค้นหาจากปลายทาง

อย่าเข้าถึง LocalResultEventBus โดยตรงภายใน UI ของหน้าจอ เพื่อให้หน้าจอที่ประกอบกันได้สามารถนำกลับมาใช้ซ้ำและทดสอบได้ แต่ให้แสดง Callback Lambda จากหน้าจอแทน ใน entryProvider ให้จัดการการเรียกกลับโดยส่งผลลัพธ์โดยใช้ LocalResultEventBus.current และกลับไปที่หน้าเดิม

คุณสามารถส่งผลลัพธ์โดยใช้คีย์ผลลัพธ์ที่ชัดเจนได้ดังนี้

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

นอกจากนี้ คุณยังส่งผลลัพธ์โดยใช้คีย์ที่ได้มาจากประเภทได้ด้วย ดังนี้

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

รับผลลัพธ์

ปลายทางสามารถใช้ผลลัพธ์โดยใช้เอฟเฟกต์ตามเหตุการณ์หรือออบเจ็กต์ที่สังเกตได้ตามสถานะ

API ลักษณะการทำงาน กรณีการใช้งานที่แนะนำ
ResultEffect Queued: ประมวลผลผลลัพธ์ทั้งหมดที่ส่งสำหรับคีย์ตามลำดับ เหตุการณ์ที่เกิดขึ้นครั้งเดียวและผลข้างเคียง (เช่น การแสดงแถบแสดงสถานะหรือการส่งต่อไปยัง ViewModel)
conflateAsState Conflated: ละทิ้งผลลัพธ์ระดับกลางและเก็บเฉพาะผลลัพธ์ล่าสุดเป็น Compose State ตัวแก้ไขสถานะ UI แบบ Lightweight (เช่น แท็กตัวกรองที่ใช้งานอยู่หรือการลบล้างการเลือก)

จัดการเหตุการณ์ที่เกิดขึ้นครั้งเดียวด้วย ResultEffect

ใช้ ResultEffect เมื่อจัดการเหตุการณ์ที่เกิดขึ้นครั้งเดียว เช่น การทริกเกอร์การวิเคราะห์ การแสดงแถบแสดงข้อความสั้นๆ หรือการส่งต่อผลลัพธ์ไปยัง ViewModel

ResultEffect จะรักษาคิวสำหรับผลลัพธ์ที่เข้ามา หากมีการส่งผลลัพธ์หลายรายการสำหรับคีย์หนึ่งๆ ResultEffect จะประมวลผลผลลัพธ์แต่ละรายการตามลำดับที่ส่ง นอกจากนี้ ResultEffect ยังทำงานในขอบเขต Coroutine ซึ่งช่วยให้คุณเรียกใช้ฟังก์ชันการระงับได้โดยตรงภายในเนื้อหาเอฟเฟกต์

คุณสามารถรอฟังผลลัพธ์ที่เชื่อมโยงกับคีย์ผลลัพธ์ที่ชัดเจนได้ดังนี้

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

นอกจากนี้ คุณยังฟังผลลัพธ์โดยใช้คีย์ที่ได้มาจากประเภทได้ด้วย ดังนี้

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

เมื่อไปยังส่วนต่างๆ ระหว่างปลายทาง ResultEffect จะดำเนินการตามลำดับวงจรต่อไปนี้

  1. Sender emits: ปลายทางของผู้ส่งจะส่งผลลัพธ์โดยใช้ resultBus.sendResult(resultKey = "pickup_address", address) และแสดง Back Stack
  2. Receiver เข้าสู่ Composition: ปลายทาง Receiver จะกลายเป็นหน้าจอที่ใช้งานอยู่ และ ResultEffect จะเริ่มฟังผลลัพธ์
  3. Receiver processes results: ResultEffect receives and executes its effect body for each result sent for that key, processing all emissions in the order they were sent.
  4. Receiver ออกจาก Composition: เมื่อปลายทางของ Receiver ถูกนำออกจาก Back Stack แล้ว ResultEffect จะออกจาก Composition และหยุดรอผลลัพธ์

สังเกตผลลัพธ์ล่าสุดเป็นสถานะด้วย conflateAsState

เมื่อคุณต้องการเพียงค่าผลลัพธ์ล่าสุดเพื่อแก้ไขหรือกรองสถานะ UI ในเครื่องโดยตรง และต้องการให้ Compose ทำการคอมโพสใหม่โดยอัตโนมัติเมื่อใดก็ตามที่ผลลัพธ์อัปเดต ให้เรียกใช้ conflateAsState ใน ResultEventBus

คุณสามารถดูผลลัพธ์ที่เชื่อมโยงกับคีย์ผลลัพธ์ที่ชัดเจนได้ดังนี้

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

นอกจากนี้ คุณยังสังเกตผลลัพธ์โดยใช้คีย์ที่ได้มาจากประเภทได้ด้วย ดังนี้

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

เครื่องยก ResultEventBus

โดยค่าเริ่มต้น rememberResultEventBusNavEntryDecorator จะสร้างและจดจำ ResultEventBus ของตัวเองภายในโดยใช้ rememberResultEventBus

คุณสามารถสร้างและยก ResultEventBus ได้อย่างชัดเจนเมื่อต้องการทำสิ่งต่อไปนี้

  • ส่งอินสแตนซ์ ResultEventBus ไปยังคอมโพเนนต์ที่ประกอบไม่ได้หรือกราฟการแทรกทรัพยากร Dependency โดยตรง
  • ส่งหรือสังเกตผลลัพธ์จากโครงสร้างแอปที่ระดับบนสุด (เช่น แถบแอปหรือลิ้นชักการนำทาง) นอกลำดับชั้นปลายทาง

หากต้องการยก ResultEventBus ให้สร้างโดยใช้ rememberResultEventBus และส่งไปยัง 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
    )
)

จัดการและล้างผลการค้นหา

เมื่อปลายทางใช้ผลลัพธ์แบบครั้งเดียว ให้ล้างผลลัพธ์ออกจากบัสเหตุการณ์โดยใช้ removeResult ซึ่งจะป้องกันไม่ให้บัสส่งเหตุการณ์ที่ผ่านมาไปยังผู้สังเกตการณ์รายใหม่อีกครั้งเมื่อปลายทางเข้าสู่ Composition อีกครั้ง

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

คุณสามารถล้างผลลัพธ์ตามคีย์ที่ชัดเจน (resultBus.removeResult(resultKey)) หรือตามคีย์ที่ได้มาจากประเภท (resultBus.removeResult<T>()) ได้ โปรดดูรายละเอียดเกี่ยวกับการจับคู่คีย์ที่หัวข้อคีย์ผลลัพธ์

สูตรอาหาร

ดูตัวอย่างโค้ดที่รันได้แบบสมบูรณ์ซึ่งแสดงกลยุทธ์การส่งผลลัพธ์แบบต่างๆ ได้ในสูตรต่อไปนี้