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

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

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

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

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

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

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

คีย์ผลลัพธ์

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

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

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

แสดงผลลัพธ์จากปลายทาง

หากต้องการให้ Composable ของหน้าจอใช้ซ้ำและทดสอบได้ อย่าเข้าถึง LocalResultEventBus โดยตรงภายใน UI ของหน้าจอ แต่ให้แสดงผลลัพธ์ของฟังก์ชันเรียกกลับ แลมบ์ดาจากหน้าจอแทน ใน 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()
        }
    )
}

รับผลลัพธ์

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

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

จัดการกิจกรรมแบบครั้งเดียวด้วย ResultEffect

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

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

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

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. ผู้ส่งส่ง: ปลายทางของผู้ส่งจะส่งผลลัพธ์โดยใช้ resultBus.sendResult(resultKey = "pickup_address", address) และนำ ออกจาก Back Stack
  2. ผู้รับเข้าสู่การเรียบเรียง: ปลายทางของผู้รับจะกลายเป็นหน้าจอที่ใช้งานอยู่ และ ResultEffect จะเริ่มฟังผลลัพธ์
  3. ผู้รับประมวลผลผลลัพธ์: ResultEffect รับและดำเนินการกับเนื้อหาเอฟเฟกต์ สำหรับแต่ละผลลัพธ์ที่ส่งสำหรับคีย์นั้น โดยประมวลผลการปล่อยทั้งหมดตาม ลำดับที่ส่ง
  4. Receiver leaves 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
    )
}

Hoist ResultEventBus

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

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

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

หากต้องการยก 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
    )
)

จัดการและล้างผลลัพธ์

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

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>()) ดูรายละเอียดเกี่ยวกับ การจับคู่คีย์ได้ที่คีย์ผลลัพธ์

สูตรอาหาร

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