תוצאות החזרה

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

ResultEventBus מספק שני מודלים לתקשורת:

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

הגדרת אפיק אירועים של תוצאות

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

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

מקשי תוצאות

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

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

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

החזרת תוצאות מיעד

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

ניהול אירועים חד-פעמיים באמצעות ResultEffect

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

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

מתכונים

דוגמאות קוד מלאות שאפשר להריץ כדי לראות איך מעבירים תוצאות בדרכים שונות: