ניווט 3

‫Navigation 3 היא ספריית ניווט שתוכננה מאפס ל-Jetpack Compose. במדריך הזה מוסבר איך מטמיעים את Navigation 3 באפליקציות ל-Wear OS.

מושגי ליבה

  • NavKey: מזהה ניתן לסריאליזציה (serializable) ובטוח לסוג (type-safe) של יעד (מסך) באפליקציה.
  • NavBackStack: רשימה שניתנת לשינוי של מופעי NavKey שמייצגים את היסטוריית הניווט. אפשר להוסיף פריטים לרשימה ולהוציא פריטים ממנה ישירות.
  • rememberNavBackStack: קומפוזיציה שיוצרת ושומרת את מקבץ הפעילויות הקודמות (back stack) כשמתבצעים שינויים בהגדרות וכשתהליך מושבת.
  • NavDisplay: רכיב הליבה של ממשק המשתמש שעוקב אחרי מקבץ פעילויות קודמות (back stack) ומציג את המסך הפעיל.
  • EntryProvider: שפת תצורה (DSL) למיפוי שמקשרת בין NavKey לבין ממשק המשתמש בפועל של @Composable.
  • SwipeDismissableSceneStrategy: אסטרטגיה ספציפית ל-Wear שעוטפת את המסכים בתנועת החלקה לסגירה ומטפלת באנימציות מובנות של חזרה.

שלב 1: מוסיפים יחסי תלות

מוסיפים לפרויקט את יחסי התלות הנדרשים: Navigation 3,‏ Wear Compose ו-Serialization.

Groovy

dependencies {
    // Core Navigation 3 APIs
    implementation "androidx.navigation3:navigation3-runtime:1.2.0-alpha02"
    implementation "androidx.navigation3:navigation3-ui:1.2.0-alpha02"

    // Wear OS specific Navigation 3 integration
    implementation "androidx.wear.compose:compose-navigation3:1.6.1"

    // Kotlinx Serialization for type-safe routing
    implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0"
}

Kotlin

dependencies {
    // Core Navigation 3 APIs
    implementation("androidx.navigation3:navigation3-runtime:1.2.0-alpha02")
    implementation("androidx.navigation3:navigation3-ui:1.2.0-alpha02")

    // Wear OS specific Navigation 3 integration
    implementation("androidx.wear.compose:compose-navigation3:1.6.1")

    // Kotlinx Serialization for type-safe routing
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.10.0")
}

שלב 2: הגדרת יעדים (NavKeys)

מסכים מוגדרים כאובייקטים או כסוגי נתונים סדרתיים עם הקלדה חזקה, שמטמיעים את הממשק NavKey.

@Serializable
sealed interface Screen : NavKey {
    @Serializable
    data object Home : Screen

    @Serializable
    data class Details(val itemId: String) : Screen
}

שלב 3: הגדרה של NavDisplay ומקבץ פעילויות קודמות (back stack)

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

// 1. Create the persistent back stack starting at the Home screen
val backStack = rememberNavBackStack(Screen.Home)

// 2. Initialize the Wear OS swipe-to-dismiss strategy
val strategy = rememberSwipeDismissableSceneStrategy<NavKey>()

// 3. Render the NavDisplay
NavDisplay(
    backStack = backStack,
    sceneStrategies = listOf(strategy),
    entryProvider = entryProvider {
        // 4. Map keys to Composables
        entry<Screen.Home> {
            HomeScreen(
                onNavigateToDetails = { id -> backStack.add(Screen.Details(id)) }
            )
        }
        entry<Screen.Details> { key ->
            DetailsScreen(
                itemId = key.itemId,
                onBack = { backStack.removeAt(backStack.lastIndex) }
            )
        }
    }
)

שלב 4: ביצוע פעולות ניווט

מכיוון שמקבץ הפעילויות הקודמות (back stack) הוא רק MutableList מותאם אישית, הניווט פשוט מאוד. אתם מבצעים פעולות ישירות במופע backStack:

  • ניווט קדימה: backStack.add(Screen.Details("123"))
  • הקודם: backStack.removeLast() או backStack.removeLastOrNull()
  • ניקוי ואיפוס: backStack.clear(); backStack.add(Screen.Home) (או שימוש בפעולות ברשימה כדי להחליף את המערך).

שלב 5: (אופציונלי) הגדרת היקף של ViewModels ליעדים

כברירת מחדל, ViewModels מוגדרים בהיקף של Activity. ‫Navigation 3 מספק ארטיפקט ספציפי (lifecycle-viewmodel-navigation3) כדי להגדיר בבטחה את ההיקף של ViewModel ל-NavEntry במקבץ פעילויות קודמות (back stack). כשמוציאים את היעד ממקבץ פעילויות קודמות (back stack), ה-ViewModel מתנקה.

  1. מוסיפים את התלות:

    implementation("androidx.lifecycle:lifecycle-viewmodel-navigation3:...")
    
  2. מוסיפים את מעצב מאגר ה-ViewModel ל-NavDisplay של entryDecorators. כשמספקים מעצבים בהתאמה אישית כדי לשמור על מצב Compose rememberSaveable, צריך לכלול גם את SaveableStateHolderNavEntryDecorator באופן מפורש:

    NavDisplay(
        backStack = backStack,
        sceneStrategies = listOf(strategy),
        entryDecorators = listOf(
            rememberSaveableStateHolderNavEntryDecorator(),
            rememberViewModelStoreNavEntryDecorator()
        ),
        entryProvider = entryProvider {
            entry<Screen.Home> {
                // Any viewModel() requested here will be scoped to this NavEntry
                val viewModel: HomeViewModel = viewModel()
            }
        }
    )