הסבר על היסודות והטמעה שלהם

הניווט מתאר את האופן שבו המשתמשים עוברים בין חלקי האפליקציה. המשתמשים מקיימים אינטראקציה עם רכיבים בממשק המשתמש, בדרך כלל על ידי לחיצה עליהם, והאפליקציה מגיבה בהצגת תוכן חדש. אם המשתמש רוצה לחזור לתוכן הקודם, הוא משתמש בתנועת החזרה או לוחץ על הכפתור "הקודם".

מצב הניווט של המודל

דרך נוחה ליצור מודל של ההתנהגות הזו היא באמצעות מחסנית של תוכן. כשהמשתמש מנווט קדימה לתוכן חדש, התוכן הזה מתווסף לראש המחסנית. כשהם חוזרים אחורה מהתוכן הזה, הוא מוסר מהמחסנית והתוכן הקודם מוצג. במונחי ניווט, המקבץ הזה נקרא בדרך כלל מקבץ פעילויות קודמות (back stack) כי הוא מייצג את התוכן שהמשתמש יכול לחזור אליו.

כפתור פעולה במקלדת הווירטואלית (סמל וי) מוקף בעיגול אדום.
איור 1. דיאגרמה שמראה איך מקבץ הפעילויות הקודמות (back stack) משתנה עם אירועי ה-Navigation של המשתמש.

יצירת מקבץ פעילויות קודמות (back stack)

ב-Navigation 3, מקבץ הפעילויות הקודמות לא מכיל תוכן בפועל. במקום זאת, הוא מכיל הפניות לתוכן, שנקראות מפתחות. המפתחות יכולים להיות מכל סוג, אבל בדרך כלל הם מחלקות נתונים פשוטות שניתנות לסריאליזציה. לשימוש בהפניות במקום בתוכן יש את היתרונות הבאים:

  • קל לנווט על ידי דחיפת מפתחות למקבץ הפעילויות הקודמות (back stack).
  • כל עוד המפתחות ניתנים להמרה לסדרה, אפשר לשמור את מקבץ הפעילויות הקודמות באחסון מתמיד, כך שהוא לא יושפע משינויים בהגדרות ומהשבתת תהליכים. זה חשוב כי המשתמשים מצפים לצאת מהאפליקציה, לחזור אליה מאוחר יותר ולהמשיך מאותה נקודה שבה הם הפסיקו, כשהתוכן שהם צפו בו מוצג. מידע נוסף זמין במאמר שמירת מקבץ הפעילויות הקודמות (back stack).

אחד מהמושגים המרכזיים ב-Navigation 3 API הוא שאתם הבעלים של מקבץ הפעילויות הקודמות (back stack). הספרייה:

  • מצפה שמקבץ הפעילויות הקודמות (back stack) יהיה List<T> שמגובה במצב של תמונת מצב, כאשר T הוא הסוג של keys של מקבץ הפעילויות הקודמות (back stack). אפשר להשתמש ב-Any או לספק מפתחות משלכם עם סוגים חזקים יותר. כשאתם רואים את המונחים "push" או "pop", המשמעות היא הוספה או הסרה של פריטים מסוף הרשימה.
  • צופה במקבץ הפעילויות הקודמות (back stack) ומשקף את המצב שלו בממשק המשתמש באמצעות NavDisplay.

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

// Define keys that will identify content
data object ProductList
data class ProductDetail(val id: String)

@Composable
fun MyApp() {

    // Create a back stack, specifying the key the app should start with
    val backStack = remember { mutableStateListOf<Any>(ProductList) }

    // Supply your back stack to a NavDisplay so it can reflect changes in the UI
    // ...more on this below...

    // Push a key onto the back stack (navigate forward), the navigation library will reflect the change in state
    backStack.add(ProductDetail(id = "ABC"))

    // Pop a key off the back stack (navigate back), the navigation library will reflect the change in state
    backStack.removeLastOrNull()
}

פתרון מפתחות לתוכן

התוכן מעוצב ב-Navigation 3 באמצעות NavEntry, שהיא מחלקה שמכילה פונקציה קומפוזבילית. הוא מייצג יעד – פריט תוכן יחיד שהמשתמש יכול לנווט אליו וממנו.

NavEntry יכול להכיל גם מטא-נתונים – מידע על התוכן. אובייקטים של מאגרים, כמו NavDisplay, יכולים לקרוא את המטא-נתונים האלה כדי להחליט איך להציג את התוכן של NavEntry. לדוגמה, אפשר להשתמש במטא-נתונים כדי לשנות את האנימציות שמוגדרות כברירת מחדל ל-NavEntry ספציפי. ‫NavEntry metadata הוא מפה של מפתחות String לערכי Any, שמספקת אחסון נתונים רב-תכליתי.

כדי להמיר key ל-NavEntry, צריך ליצור ספק רשומות. זו פונקציה שמקבלת key ומחזירה NavEntry עבור אותו key. בדרך כלל הוא מוגדר כפרמטר למבדה כשיוצרים NavDisplay.

יש שתי דרכים ליצור ספק רשומות: ליצור פונקציית lambda באופן ישיר, או להשתמש ב-DSL של entryProvider.

יצירה ישירה של פונקציית Entry Provider

בדרך כלל יוצרים פונקציית Entry Provider באמצעות הצהרת when, עם הסתעפות לכל אחד מהמפתחות.

entryProvider = { key ->
    when (key) {
        is ProductList -> NavEntry(key) { Text("Product List") }
        is ProductDetail -> NavEntry(
            key,
            metadata = mapOf("extraDataKey" to "extraDataValue")
        ) { Text("Product ${key.id} ") }

        else -> {
            NavEntry(Unit) { Text(text = "Invalid Key: $it") }
        }
    }
}

שימוש ב-DSL של entryProvider

ה-DSL של entryProvider יכול לפשט את פונקציית הלמבדה, כי לא צריך לבדוק כל אחד מסוגי המפתחות וליצור NavEntry לכל אחד מהם. כדי לעשות את זה, משתמשים בפונקציית ה-builder‏ entryProvider. הוא כולל גם התנהגות ברירת מחדל חלופית (הצגת שגיאה) אם המפתח לא נמצא.

entryProvider = entryProvider {
    entry<ProductList> { Text("Product List") }
    entry<ProductDetail>(
        metadata = mapOf("extraDataKey" to "extraDataValue")
    ) { key -> Text("Product ${key.id} ") }
}

שימו לב לפרטים הבאים בקטע הקוד:

  • ‫entry משמש להגדרת NavEntry עם הסוג הנתון והתוכן הניתן להרכבה
  • הפונקציה entry מקבלת פרמטר metadata כדי להגדיר את NavEntry.metadata

הצגת מקבץ הפעילויות הקודמות (back stack)

מקבץ הפעילויות הקודמות מייצג את מצב הניווט של האפליקציה. בכל פעם שמקבץ הפעילויות הקודמות משתנה, ממשק המשתמש של האפליקציה צריך לשקף את המצב החדש של מקבץ הפעילויות הקודמות. ב-Navigation 3, ‏NavDisplay צופה במקבץ הפעילויות הקודמות ומעדכן את ממשק המשתמש בהתאם. צריך ליצור אותו עם הפרמטרים הבאים:

  • מקבץ הפעילויות הקודמות (back stack) – צריך להיות מסוג SnapshotStateList<T>, כאשר T הוא סוג המפתחות של מקבץ הפעילויות הקודמות. זהו List שניתן לצפייה, כך שהוא מפעיל הרכבה מחדש של NavDisplay כשהוא משתנה.
  • ‫entryProvider להמרת המפתחות במקבץ הפעילויות הקודמות (back stack) לאובייקטים מסוג NavEntry.
  • אפשר גם לספק למבדה לפרמטר onBack. הפונקציה הזו מופעלת כשהמשתמש מפעיל אירוע חזרה.

בדוגמה הבאה אפשר לראות איך יוצרים NavDisplay.

data object Home
data class Product(val id: String)

@Composable
fun NavExample() {

    val backStack = remember { mutableStateListOf<Any>(Home) }

    NavDisplay(
        backStack = backStack,
        onBack = { backStack.removeLastOrNull() },
        entryProvider = { key ->
            when (key) {
                is Home -> NavEntry(key) {
                    ContentGreen("Welcome to Nav3") {
                        Button(onClick = {
                            backStack.add(Product("123"))
                        }) {
                            Text("Click to navigate")
                        }
                    }
                }

                is Product -> NavEntry(key) {
                    ContentBlue("Product ${key.id} ")
                }

                else -> NavEntry(Unit) { Text("Unknown route") }
            }
        }
    )
}

כברירת מחדל, NavDisplay מציג את NavEntry העליון במקבץ הפעילויות הקודמות (back stack) בפריסה של חלונית אחת. בהקלטה הבאה מוצגת האפליקציה הזו בפעולה:

התנהגות ברירת המחדל של `NavDisplay` עם שני יעדים.
איור 2. NavDisplay פעולת ברירת מחדל עם שני יעדים.

מחזור החיים של היעד

‫NavDisplay משתמש בLifecycleOwners מותאמים אישית כדי להגביל את מצב מחזור החיים של NavEntry על סמך אילוצים ברמת ה-Scene ואילוצים ברמת ה-Entry.

מידע נוסף על מחזורי חיים ב-Compose זמין במאמר מחזור חיים ב-Jetpack Compose.

אילוצים במחזור החיים ברמת הסצנה

‫NavDisplay מנהל את מחזור החיים של ה-Scene הפעילים. המגבלות ברמת הסצנה נקבעות באופן הבא:

בסצנות שאינן שכבות-על:

  • ‫RESUMED: מותר רק כשהמעבר בין הסצנות הסתיים ואין סצנות שכבת-על פעילות שמוצגות מעליה.
  • ‫STARTED: מוגבל ל-STARTED בזמן מעבר בין סצנות, למשל כשמנווטים קדימה או אחורה, או כשהסצנה מכוסה בשכבת-על.

בסצנות שכבת-על, כמו תיבות דו-שיח או גיליונות תחתונים:

  • ‫RESUMED: מותר רק לסצנת שכבת-העל העליונה ביותר שפעילה כרגע.
  • ‫STARTED: מוגבל ל-STARTED לכל סצנת שכבת-על בסיסית שמכוסה על ידי שכבת-על חדשה יותר.

מצב מחזור חיים בסיסי

הספרייה מנהלת את מצב מחזור החיים המקסימלי של כל NavEntry בהתאם לנוכחות שלו במקבץ הפעילויות הקודמות (back stack):

  • ‫RESUMED: אם הרשומה קיימת במקבץ הפעילויות הקודמות הנוכחי, מחזור החיים שלה יכול להגיע עד RESUMED (בכפוף למגבלה ברמת הסצנה).
  • CREATED: אם הרשומה כבר לא נמצאת במקבץ הפעילויות הקודמות (back stack), למשל כשהיא הוסרה אבל עדיין מוצגת במסך בזמן שהאנימציה מסתיימת, הספרייה מגבילה את מחזור החיים שלה ל-CREATED. המגבלה הזו מבטיחה שרשומות ברקע או רשומות שיוצאות יפסיקו לבצע פעולות פעילות, כמו איסוף תהליכים או הפעלת פונקציות קורוטין שקשורות למצבים RESUMED או STARTED, בזמן שהן מסיימות את מעברי היציאה שלהן.

איך הם משתלבים

לדוגמה, מצב מחזור החיים הסופי של NavEntry נקבע באופן הבא:

תרחיש מכסה ברמת הסצנה מכסה בסיסית מכסה בפועל
כניסה פעילה, מסך קבוע (ללא מעברים או שכבות-על) RESUMED RESUMED RESUMED
רשומה פעילה, במהלך מעבר (ניווט אל או מתוך) STARTED RESUMED STARTED
רשומה פעילה, מכוסה בשכבת-על (לדוגמה, תיבת דו-שיח פתוחה) STARTED RESUMED STARTED
רשומה קופצת, אנימציה החוצה STARTED או RESUMED CREATED CREATED

סיכום של כל המידע

הדיאגרמה הבאה מראה איך הנתונים זורמים בין האובייקטים השונים ב-Navigation 3:

תרשים שממחיש איך הנתונים זורמים בין האובייקטים השונים ב-Navigation 3.
איור 3. תרשים שמראה איך הנתונים זורמים דרך אובייקטים שונים ב-Navigation 3.
  1. אירועי ניווט יוזמים שינויים. מפתחות מתווספים למקבץ הפעילויות הקודמות או מוסרים ממנו בתגובה לאינטראקציות של המשתמשים.

  2. שינוי במצב של מקבץ הפעילויות הקודמות (back stack) מפעיל אחזור תוכן. ה-NavDisplay (רכיב קומפוזבילי שמעבד מקבץ פעילויות קודמות) צופה במקבץ הפעילויות הקודמות. בהגדרת ברירת המחדל, הוא מציג את הרשומה העליונה במקבץ הפעילויות הקודמות (back stack) בפריסה של חלונית אחת. כשהמפתח העליון במקבץ הפעילויות הקודמות משתנה, NavDisplay משתמש במפתח הזה כדי לבקש את התוכן התואם מספק הרשומה.

  3. ספק הרשומה מספק את התוכן. ספק הרשומה הוא פונקציה שמפענחת מפתח ל-NavEntry. אחרי קבלת מפתח מ-NavDisplay, ספק הרשומה מספק את ה-NavEntry המשויך, שמכיל גם את המפתח וגם את התוכן.

  4. התוכן מוצג. ה-NavDisplay מקבל את ה-NavEntry ומציג את התוכן.