תוצאות החזרה

החל מגרסה 1.2.0 של Navigation 3, אפשר להחזיר תוצאות מיעדים באמצעות ה-API של ResultEventBus.

ב-ResultEventBus יש שני מודלים של תקשורת:

  • תוצאות מבוססות-אירועים: לאירועים זמניים וחד-פעמיים (כמו הצגת סרגל הודעות לאישור או הפעלת תוצאת לוואי) באמצעות ResultEffect.
  • תוצאות מבוססות-מצב: כדי לצפות בתוצאה האחרונה כ-Compose State באמצעות conflateAsState.

הגדרת אוטובוס האירועים של התוצאות

כדי להפוך את ResultEventBus לזמין ליעדים הניתנים ליצירה, מוסיפים את rememberResultEventBusNavEntryDecorator לרשימת ה-decorators שמועברים אל NavDisplay. כך לתוכן של כל יעד יש LocalResultEventBus מקומי לקומפוזיציה.

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

מקשי התוצאות

הפונקציה ResultEventBus מזהה ומנתבת כל תוצאה באמצעות מפתח. השולחים והמקבלים מתאימים את התוצאות באמצעות אותו מפתח.

אפשר לציין מפתחות תוצאות בשתי דרכים:

  • מפתחות מפורשים: אתם יכולים לציין מפתח מפורש (כמו resultKey = "pickup_address"). כדאי להשתמש במפתחות מפורשים כשמחזירים סוגים נפוצים (כמו String, Boolean או פרימיטיבים), או כשכמה יעדים מחזירים מופעים שונים של אותו סוג נתונים.
  • מפתחות שנגזרים מסוג: כשלא מציינים מפתח מפורש, ResultEventBus יוצר באופן אוטומטי מפתח באמצעות הייצוג של toString של KClass של סוג התוצאה (כמו Contact::class.toString()). משתמשים במפתחות שנגזרים מסוג בשביל סוגי נתונים נפרדים וספציפיים לדומיין.

הצגת תוצאות מיעד מסוים

כדי שרכיבי ה-composable של המסך יהיו ניתנים לשימוש חוזר ולבדיקה, אל תיגשו אל LocalResultEventBus ישירות מתוך ממשק המשתמש של המסך. במקום זאת, חשפו פונקציות למבדה של קריאה חוזרת (callback) מהמסך. ב-entryProvider, מטפלים בקריאה החוזרת (callback) על ידי שליחת התוצאה באמצעות 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 בתור: עיבוד כל התוצאות שהופקו למפתח לפי הסדר. אירועים חד-פעמיים ותופעות לוואי (כמו הצגת סרגל חטיפים או העברה אל ViewModel).
conflateAsState Conflated: המערכת מתעלמת מתוצאות הביניים ושומרת רק את התוצאה האחרונה בתור Compose State. משתנים קלים של מצב ממשק המשתמש (כמו תגי סינון פעילים או שינויים בבחירה).

טיפול באירועים חד-פעמיים באמצעות 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. השולח פולט: יעד השולח שולח תוצאה באמצעות resultBus.sendResult(resultKey = "pickup_address", address) ומקפיץ את מקבץ הפעילויות הקודמות (back stack).
  2. המקלט נכנס לקומפוזיציה: יעד המקלט הופך למסך הפעיל, ו-ResultEffect מתחיל להאזין לתוצאות.
  3. המקבל מעבד את התוצאות: ResultEffect מקבל ומבצע את גוף האפקט שלו לכל תוצאה שנשלחה עבור המפתח הזה, ומעבד את כל הפליטות לפי הסדר שבו הן נשלחו.
  4. היעד המקבל יוצא מהקומפוזיציה: כשמסירים את היעד המקבל ממקבץ הפעילויות הקודמות, ResultEffect יוצא מהקומפוזיציה ומפסיק להאזין לתוצאות.

צפייה בתוצאות האחרונות כמצב עם conflateAsState

אם אתם צריכים רק את ערך התוצאה האחרון כדי לשנות או לסנן ישירות את מצב ממשק המשתמש המקומי, ואתם רוצים ש-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 ישירות לרכיבים שלא ניתנים להרכבה או לתרשימים של הזרקת תלויות.
  • לשלוח או לצפות בתוצאות מהרכיבים ברמה העליונה של האפליקציה (כמו סרגל האפליקציה או חלונית הזזה לניווט) מחוץ להיררכיית היעדים.

כדי להעביר את 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. כך מונעים מהאפיק לשלוח מחדש אירועים קודמים למשקיפים חדשים כשהיעדים נכנסים מחדש לקומפוזיציה:

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>()). לפרטים על התאמת מפתחות, ראו מפתחות תוצאות.

מתכונים

בדוגמאות הבאות של קוד מלא שאפשר להריץ, מוצגות שיטות שונות להעברת תוצאות: