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