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