Fragments ו-Kotlin DSL

רכיב הניווט מספק שפה ספציפית לדומיין מבוססת-Kotlin, או DSL, המסתמכת על מודל בטוח מסוג Kotlin Builders הקצר הזה. התשובות שלך יעזרו לנו להשתפר. ה-API הזה מאפשר לכתוב את התרשים באופן הצהרתי בקוד Kotlin מאשר בתוך משאב XML. היא יכולה להיות שימושית אם רוצים ליצור ניווט דינמי. לדוגמה, האפליקציה יכולה להוריד ולשמור במטמון להגדרת ניווט משירות אינטרנט חיצוני, ואז להשתמש בו כדי ליצור באופן דינמי תרשים ניווט onCreate().

יחסי תלות

כדי להשתמש ב-Kotlin DSL עם Fragments, יש להוסיף את התלות הבאה קובץ build.gradle:

מגניב

dependencies {
    def nav_version = "2.8.0"

    api "androidx.navigation:navigation-fragment-ktx:$nav_version"
}

Kotlin

dependencies {
    val nav_version = "2.8.0"

    api("androidx.navigation:navigation-fragment-ktx:$nav_version")
}

יצירת תרשים

הנה דוגמה בסיסית שמבוססת על החמנייה app. בשביל זה לדוגמה, יש לנו שני יעדים: home ו-plant_detail. home היעד מוצג כשהמשתמש מפעיל את האפליקציה בפעם הראשונה. היעד הזה מציג רשימה של צמחים מהגינה של המשתמש. כשהמשתמש בוחר באחת מהאפשרויות הבאות: הצמחים, האפליקציה מנווטת ליעד plant_detail.

איור 1 מציג את היעדים האלה יחד עם הארגומנטים הנדרשים יעד אחד (plant_detail) ופעולה, to_plant_detail, שהאפליקציה משתמשת בהם כדי לנווט מhome אל plant_detail.

אפליקציית Sunflower כוללת שני יעדים
            שמחבר ביניהם.
איור 1. לאפליקציית Sunflower יש שני יעדים: home וגם plant_detail, יחד עם פעולה שמחבר ביניהם.

אירוח תרשים Nav של Kotlin DSL

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

<!-- activity_garden.xml -->
<FrameLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto">
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <androidx.fragment.app.FragmentContainerView
        android:id="@+id/nav_host"
        android:name="androidx.navigation.fragment.NavHostFragment"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        app:defaultNavHost="true" />

</FrameLayout>

שימו לב שהמאפיין app:navGraph לא מוגדר בדוגמה הזו. התרשים לא מוגדר כמשאב ב- התיקייה res/navigation, כך שצריך להגדיר אותה כחלק מonCreate() במהלך הפעילות.

ב-XML, פעולה קושרת מזהה יעד עם ארגומנט אחד או יותר. עם זאת, בעת שימוש ב-DSL של ניווט, נתיב יכול להכיל ארגומנטים כחלק את המסלול. המשמעות היא שאין קונספט של פעולות כשמשתמשים ב-DSL.

השלב הבא הוא להגדיר את הנתיבים שבהם תשתמשו בזמן הגדרת גרפי.

יצירת מסלולים לתרשים

תרשימי ניווט מבוססי XML מנותחים כחלק בתהליך ה-build של Android. נוצר קבוע מספרי לכל id המוגדר בגרף. המזהים הסטטיים שנוצרו בזמן build לא זמין בעת יצירת תרשים הניווט בזמן הריצה, כדי שה-DSL לניווט משתמש באפשרויות סידוריות סוגים במקום המזהים. כל נתיב מיוצג על ידי סוג ייחודי.

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

@Serializable data object Home
@Serializable data class Plant(val id: String)

אחרי שמגדירים את המסלולים, אפשר ליצור את תרשים הניווט.

val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
    startDestination = Home
) {
    fragment<HomeFragment, Home> {
        label = resources.getString(R.string.home_title)
    }
    fragment<PlantDetailFragment, PlantDetail> {
        label = resources.getString(R.string.plant_detail_title)
    }
}

בדוגמה זו, שני יעדי מקטעים מוגדרים באמצעות fragment() הפונקציה Builder DSL. בפונקציה הזו נדרשים שני סוגים ארגומנטים הקצר הזה. התשובות שלך יעזרו לנו להשתפר.

הראשונה, כיתה Fragment שמספקת את ממשק המשתמש של היעד הזה. להגדרה הזו יש אותה השפעה כמו הגדרת המאפיין android:name ביעדי מקטעים מוגדרים באמצעות XML.

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

הפונקציה גם מקבלת lambda אופציונליות לתצורה נוספת, כמו וגם תווית היעד, וגם פונקציות של builder מוטמע ארגומנטים וקישורי עומק.

לבסוף, אפשר לנווט מhome אל plant_detail באמצעות NavController.navigate() שיחות:

private fun navigateToPlant(plantId: String) {
   findNavController().navigate(route = PlantDetail(id = plantId))
}

ב-PlantDetailFragment, אפשר לקבל את ארגומנטים של ניווט באמצעות הפונקציה הנוכחי NavBackStackEntry והתקשרות toRoute עליו כדי לקבל את המופע של הנתיב.

val plantDetailRoute = findNavController().getBackStackEntry<PlantDetail>().toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

אם PlantDetailFragment משתמש ב-ViewModel, צריך להשיג את המופע של המסלול באמצעות SavedStateHandle.toRoute.

val plantDetailRoute = savedStateHandle.toRoute<PlantDetail>()
val plantId = plantDetailRoute.id

שאר המדריך מתאר רכיבים נפוצים בתרשים הניווט, יעדים, וכיצד להשתמש בהם כאשר אתם יוצרים את התרשים.

יעדים

Kotlin DSL מספק תמיכה מובנית לשלושה סוגי יעדים: היעדים Fragment, Activity ו-NavGraph, ולכל אחד מהם יש יעד משלו פונקציית התוספים המוטבעת זמינה לבנייה ולהגדרה של היעד.

יעדי מקטעים

fragment() אפשר להגדיר פרמטרים של פונקציית DSL באמצעות מחלקת המקטעים של ממשק המשתמש, סוג המסלול שמשמש לזיהוי ייחודי של היעד הזה, ואחריו עמודת lambda שבו ניתן לספק הגדרות אישיות נוספות כפי שמתואר בקטע ניווט בקטע של תרשים Kotlin DSL.

fragment<MyFragment, MyRoute> {
   label = getString(R.string.fragment_title)
   // custom argument types, deepLinks
}

יעד הפעילות

activity() פונקציית DSL לוקחת פרמטר מסוג 'סוג' עבור המסלול, אבל לא מקבלים פרמטר כל סיווג פעילות מוטמע. במקום זאת, צריך להגדיר ערך אופציונלי בשדה activityClass למבדה בסוף. הגמישות הזו מאפשרת להגדיר יעד לפעילות פעילות שצריך להפעיל באמצעות מודל הענקת גישה משתמע Intent, לא יהיה הגיוני. בדומה ליעדים עם מקטעים, אפשר גם להגדיר תווית, ארגומנטים בהתאמה אישית וקישורי עומק.

activity<MyRoute> {
   label = getString(R.string.activity_title)
   // custom argument types, deepLinks...

   activityClass = MyActivity::class
}

navigation() ניתן להשתמש בפונקציית DSL כדי ליצור ניווט בתצוגת עץ גרפי. הפונקציה הזו לוקחת סוג של הנתיב שיוקצו לתרשים הזה. יתקבלו גם שני ארגומנטים: את המסלול של היעד ההתחלתי בגרף, ו-lambda להגדיר את התרשים. רכיבים חוקיים כוללים יעדים אחרים, ארגומנט בהתאמה אישית , קישורי עומק ותווית תיאורית היעד. התווית הזו יכולה להיות שימושית לקישור תרשים הניווט לרכיבי ממשק משתמש באמצעות NavigationUI

@Serializable data object HomeGraph
@Serializable data object Home

navigation<HomeGraph>(startDestination = Home) {
   // label, other destinations, deep links
}

יעדים מותאמים אישית

אם אתם משתמשים בסוג יעד חדש שלא תומך ישירות ב-Kotlin DSL, אפשר להוסיף את היעדים האלה ה-DSL של Kotlin באמצעות addDestination():

// The NavigatorProvider is retrieved from the NavController
val customDestination = navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}
addDestination(customDestination)

לחלופין, אפשר להשתמש גם באופרטור unary Plus כדי להוסיף יעד מובנה ישירות לתרשים:

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

הצגת ארגומנטים של יעד

אפשר להגדיר ארגומנטים של יעד כחלק מסיווג הנתיב. האפשרויות האלה יכולות להיות מוגדרים באותו אופן שבו מגדירים כל שיעור ב-Kotlin. הארגומנטים הנדרשים הם מוגדרים כסוגים שאינם ערכי null וארגומנטים אופציונליים מוגדרים כברירת מחדל. ערכים.

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

הסדרה של קוטלין יישומי פלאגין יוצרת באופן אוטומטי שיטות סריאליזציה לנתונים בסיסיים כאשר ההערה @Serializable מתווספת לאובייקט.

@Serializable
data class MyRoute(
  val id: String,
  val myList: List<Int>,
  val optionalArg: String? = null
)

fragment<MyFragment, MyRoute>

מתן סוגים מותאמים אישית

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

לדוגמה, מסלול שמשמש להגדרה של מסך חיפוש יכול להכיל מחלקה שמייצג את הפרמטרים של החיפוש:

@Serializable
data class SearchRoute(val parameters: SearchParameters)

@Serializable
data class SearchParameters(
  val searchQuery: String,
  val filters: List<String>
)

אפשר לכתוב NavType בהתאמה אישית כך:

val SearchParametersType = object : NavType<SearchParameters>(
  isNullableAllowed = false
) {
  override fun put(bundle: Bundle, key: String, value: SearchParameters) {
    bundle.putParcelable(key, value)
  }
  override fun get(bundle: Bundle, key: String): SearchParameters {
    return bundle.getParcelable(key) as SearchParameters
  }

  override fun serializeAsValue(value: SearchParameters): String {
    // Serialized values must always be Uri encoded
    return Uri.encode(Json.encodeToString(value))
  }

  override fun parseValue(value: String): SearchParameters {
    // Navigation takes care of decoding the string
    // before passing it to parseValue()
    return Json.decodeFromString<SearchParameters>(value)
  }
}

לאחר מכן אפשר להשתמש בו ב-Kotlin DSL כמו כל סוג אחר:

fragment<SearchFragment, SearchRoute> {
    label = getString(R.string.plant_search_title)
    typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
}

כשמנווטים ליעד, יוצרים מופע של המסלול:

val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))

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

val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters

קישורי עומק

אפשר להוסיף קישורי עומק לכל יעד, בדיוק כמו שמוסיפים קישורי עומק תרשים ניווט. כל אותם התהליכים שמוגדרים ביצירת קישור עומק של יעד חלים על התהליך ליצירת קישור עומק באמצעות Kotlin DSL.

כשיוצרים קישור עומק מרומז אבל אין לכם משאב ניווט בפורמט XML שניתן לנתח רכיבי <deepLink>. לכן, לא ניתן להסתמך על הצבת <nav-graph> בקובץ AndroidManifest.xml וצריך להוסיף במקום זאת כוונת רכישה מסננים לפעילות שלכם באופן ידני. הכוונה המסנן שמספקים צריך להתאים לנתיב הבסיסי, לפעולה ול-mimetype של בקישורי העומק של האפליקציה.

קישורי עומק מתווספים ליעד על ידי קריאה לפונקציה deepLink שבפנים הלמבדה של היעד. הוא מקבל את המסלול כסוג פרמטר, basePath לנתיב הבסיס של כתובת ה-URL שמשמשת לקישור העומק.

ניתן גם להוסיף פעולה ו-mimetype באמצעות deepLinkBuilder למבדה בסוף.

הדוגמה הבאה יוצרת URI של קישור עומק עבור היעד Home.

@Serializable data object Home

fragment<HomeFragment, Home>{
  deepLink<Home>(basePath = "www.example.com/home"){
    // Optionally, specify the action and/or mime type that this destination
    // supports
    action = "android.intent.action.MY_ACTION"
    mimeType = "image/*"
  }
}

פורמט URI

פורמט ה-URI של קישור עומק נוצר באופן אוטומטי משדות הנתיב באמצעות הכללים הבאים:

  • הפרמטרים הנדרשים מתווספים כפרמטרים של נתיב (לדוגמה: /{id})
  • פרמטרים עם ערך ברירת מחדל (פרמטרים אופציונליים) מצורפים כ-query פרמטרים (לדוגמה: ?name={name})
  • אוספים מתווספים כפרמטרים של שאילתה (לדוגמה: ?items={value1}&items={value2})
  • סדר הפרמטרים תואם לסדר השדות במסלול

לדוגמה, סוג המסלול הבא:

@Serializable data class PlantDetail(
  val id: String,
  val name: String,
  val colors: List<String>,
  val latinName: String? = null,
)

נוצר פורמט URI של:

basePath/{id}/{name}/?colors={color1}&colors={color2}&latinName={latinName}

אין הגבלה על מספר קישורי העומק שאפשר להוסיף. בכל פעם שמתקשרים deepLink() קישור עומק חדש מצורף לרשימה שנשמרה עבור היעד הזה.

מגבלות

הפלאגין Safe Args הוא לא תואם ל-Kotlin DSL, כי הפלאגין מחפש קובצי משאבים של XML כדי ליצור Directions ו-Arguments מחלקות.