רכיב הניווט מספק שפה ספציפית לדומיין מבוססת-Kotlin, או
DSL, שמסתמכת על
type-safe builders.
ה-API הזה מאפשר להרכיב באופן הצהרתי את התרשים בקוד Kotlin שלך,
במקום בתוך משאב XML. היא יכולה להיות שימושית אם רוצים ליצור
לניווט דינמי באפליקציה. לדוגמה, האפליקציה יכולה להוריד וגם
לשמור תצורת ניווט משירות אינטרנט חיצוני ואז להשתמש
את התצורה הזו כדי ליצור באופן דינמי תרשים ניווט
onCreate()
.
יחסי תלות
כדי להשתמש ב-Kotlin DSL, צריך להוסיף את התלות הבאה ל-
קובץ build.gradle
:
מגניב
dependencies { def nav_version = "2.7.7" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.7.7" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
יצירת תרשים
נתחיל בדוגמה בסיסית שמבוססת על
אפליקציית 'חמנית'. בשביל זה
לדוגמה, יש לנו שני יעדים: home
ו-plant_detail
. home
היעד מוצג כשהמשתמש מפעיל את האפליקציה בפעם הראשונה. היעד הזה
מציג רשימה של צמחים מהגינה של המשתמש. כשהמשתמש בוחר באחת מהאפשרויות הבאות:
הצמחים, האפליקציה מנווטת ליעד plant_detail
.
איור 1 מציג את היעדים האלה יחד עם הארגומנטים הנדרשים
יעד אחד (plant_detail
) ופעולה, to_plant_detail
, שהאפליקציה משתמשת בהם
כדי לנווט מhome
אל plant_detail
.
![אפליקציית Sunflower כוללת שני יעדים
שמחבר ביניהם.](https://developer.android.com/static/images/guide/navigation/navigation-kotlin-dsl-1.png?authuser=0&hl=he)
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 של ניווט נעשה שימוש במחרוזות נתיב במקום במזהים. כל נתיב מיוצג על ידי
מחרוזת ייחודית ומומלץ להגדיר אותם כקבועים כדי לצמצם
את הסיכון לבאגים שקשורים להקלדה.
כשמדובר בארגומנטים, שמובנה במחרוזת המסלול. פיתוח הלוגיקה הזו לאורך המסלול יכול, שוב, לצמצם את הסיכון וכל מיני באגים שקשורים לשגיאות הקלדה.
object nav_routes {
const val home = "home"
const val plant_detail = "plant_detail"
}
object nav_arguments {
const val plant_id = "plant_id"
const val plant_name = "plant_name"
}
יצירת תרשים באמצעות Nav GraphBuilder DSL
אחרי שמגדירים את הקבועים, אפשר ליצור את רכיב הניווט גרפי.
val navController = findNavController(R.id.nav_host_fragment)
navController.graph = navController.createGraph(
startDestination = nav_routes.home
) {
fragment<HomeFragment>(nav_routes.home) {
label = resources.getString(R.string.home_title)
}
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = resources.getString(R.string.plant_detail_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
}
}
}
בדוגמה זו, ה-lambda בסוף מגדיר שני יעדי מקטעים באמצעות התג
fragment()
הפונקציה Builder DSL. הפונקציה הזו מחייבת מחרוזת נתיב ליעד
שמתקבל מהקבועים. הפונקציה גם מקבלת
lambda להגדרות נוספות, כמו תווית היעד
והפונקציות של ה-builder המוטמע לארגומנטים ולקישורי עומק.
הסיווג Fragment
מנהל את ממשק המשתמש של כל יעד, מועבר כסוג פרמטר בתוך
סוגריים זוויתיים (<>
). הפעולה הזו משפיעה כמו הגדרה של android:name
ביעדי מקטעים שמוגדרים באמצעות XML.
ניווט באמצעות תרשים ה-DSL של Kotlin
לבסוף, אפשר לנווט מhome
אל plant_detail
באמצעות
NavController.navigate()
שיחות:
private fun navigateToPlant(plantId: String) {
findNavController().navigate("${nav_routes.plant_detail}/$plantId")
}
בפונקציה PlantDetailFragment
אפשר לקבל את ערך הארגומנט כמו שמוצג
בדוגמה הבאה:
val plantId: String? = arguments?.getString(nav_arguments.plant_id)
פרטים על האופן שבו ניתן לספק ארגומנטים במהלך ניווט זמינים בקטע הוספת ארגומנטים של יעד.
שאר המדריך מתאר רכיבים נפוצים בתרשים הניווט, יעדים, וכיצד להשתמש בהם כאשר אתם יוצרים את התרשים.
יעדים
Kotlin DSL מספק תמיכה מובנית לשלושה סוגי יעדים:
היעדים Fragment
, Activity
ו-NavGraph
, ולכל אחד מהם יש יעד משלו
פונקציית התוספים המוטבעת זמינה לבנייה ולהגדרה של
היעד.
יעדי מקטעים
fragment()
אפשר לתת פרמטרים של פונקציית DSL לסיווג המקטעים המוטמעים והיא לוקחת
מחרוזת נתיב ייחודית שצריך להקצות ליעד הזה, ואחריה lambda,
אפשר לספק הגדרות אישיות נוספות כמו שמתואר
ניווט באמצעות תרשים ה-DSL של Kotlin
.
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
יעד הפעילות
activity()
פונקציית DSL משתמשת במחרוזת נתיב ייחודית כדי להקצות ליעד הזה, אבל
לא מוגדרים כפרמטרים של סיווג פעילות מיושמת. במקום זאת, צריך להגדיר
אופציונלי activityClass
ב-lambda בסוף. הגמישות הזו מאפשרת
להגדיר יעד פעילות לפעילות שתופעל באמצעות
אובייקט Intent מרומז, שבו
אין משמעות לסיווג פעילות בוטה. בדומה ליעדים עם מקטעים,
אפשר גם להגדיר תווית, ארגומנטים וקישורי עומק.
activity(nav_routes.route_name) {
label = getString(R.string.activity_title)
// arguments, deepLinks...
activityClass = ActivityDestination::class
}
היעד בתרשים הניווט
navigation()
אפשר להשתמש בפונקציית DSL כדי
תרשים ניווט מוטמע.
הפונקציה הזו לוקחת שלושה ארגומנטים: נתיב אל
להקצות לתרשים, את נתיב היעד ההתחלתי של הגרף
lambda כדי להגדיר את התרשים עוד יותר. האלמנטים החוקיים כוללים יעדים אחרים,
ארגומנטים, קישורי עומק
תווית תיאורית ליעד.
התווית הזו יכולה להיות שימושית לקישור תרשים הניווט לממשק המשתמש
רכיבים באמצעות
NavigationUI
navigation("route_to_this_graph", nav_routes.home) {
// label, other destinations, deep links
}
יעדים מותאמים אישית
אם משתמשים
סוג יעד חדש שלא כולל
לתמיכה ישירה ב-Kotlin DSL, אפשר להוסיף את היעדים האלה ל-Kotlin
DSL באמצעות 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
}
הצגת ארגומנטים של יעד
כל יעד יכול להגדיר ארגומנטים שהם אופציונליים או שהם נדרשים. פעולות
אפשר להגדיר באמצעות
argument()
בפונקציה NavDestinationBuilder
, שהיא המחלקה הבסיסית
הסוגים של הכלי ליצירת יעדים. הפונקציה הזו לוקחת את שם הארגומנט כמחרוזת
ולמבדה שמשמשת לבנייה ולהגדרה
NavArgument
בתוך lambda ניתן לציין את סוג הנתונים של הארגומנט, ערך ברירת מחדל אם רלוונטי, והאם הוא אפסי.
fragment<PlantDetailFragment>("${nav_routes.plant_detail}/{${nav_arguments.plant_id}}") {
label = getString(R.string.plant_details_title)
argument(nav_arguments.plant_id) {
type = NavType.StringType
defaultValue = getString(R.string.default_plant_id)
nullable = true // default false
}
}
אם צוין defaultValue
, אפשר להסיק את הסוג. אם גם defaultValue
ומציינים type
, הסוגים חייבים להתאים. לצפייה
מאמרי עזרה של NavType
הרשימה המלאה של סוגי הארגומנטים הזמינים.
מתן סוגים מותאמים אישית
סוגים מסוימים, כמו
ParcelableType
וגם
SerializableType
,
לא תומכות בניתוח ערכים מהמחרוזות שמשמשות למסלולים או לקישורי עומק.
הסיבה לכך היא שהם לא מסתמכים על השתקפות בזמן הריצה. על ידי הוספה של
NavType
, יש לך אפשרות לקבוע בדיוק איך הסוג שלך ינותח ממסלול, או
קישור עומק. כך אפשר להשתמש
הסדרה של קוטלין (Kotlin) או אחר
ספריות כדי לספק קידוד ופענוח ללא השתקפות מהסוג המותאם אישית.
לדוגמה, סיווג נתונים שמייצג פרמטרים של חיפוש שמועברים
מסך החיפוש יכול להטמיע גם את Serializable
(כדי לספק את
תמיכה בקידוד/פענוח) וב-Parcelize
(לתמיכה בשמירה ובשחזור
מ-Bundle
):
@Serializable
@Parcelize
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>(nav_routes.plant_search) {
label = getString(R.string.plant_search_title)
argument(nav_arguments.search_parameters) {
type = SearchParametersType
defaultValue = SearchParameters("cactus", emptyList())
}
}
השדה NavType
כולל גם את הכתיבה וגם את הקריאה בכל שדה,
כלומר, חייבים להשתמש ב-NavType
גם כשמנווטים אל
כדי להבטיח שהפורמטים תואמים:
val params = SearchParameters("rose", listOf("available"))
val searchArgument = SearchParametersType.serializeAsValue(params)
navController.navigate("${nav_routes.plant_search}/$searchArgument")
אפשר לקבל את הפרמטר מהארגומנטים ביעד:
val params: SearchParameters? = arguments?.getParcelable(nav_arguments.search_parameters)
קישורי עומק
אפשר להוסיף קישורי עומק לכל יעד, בדיוק כמו שמוסיפים קישורי עומק תרשים ניווט. כל אותם התהליכים שמוגדרים ב יצירת קישור עומק ליעד חלות על התהליך של יצירת קישור עומק מפורש באמצעות Kotlin DSL.
כשיוצרים קישור עומק מרומז
אבל אין לכם משאב ניווט בפורמט XML שניתן לנתח
רכיבי <deepLink>
. לכן, לא ניתן להסתמך על הצבת <nav-graph>
בקובץ AndroidManifest.xml
, וצריך להוסיף במקום זאת
מסנני כוונת רכישה לפעילות שלכם באופן ידני.
מסנן Intent שאתם מציינים צריך להתאים לתבנית כתובת האתר הבסיסית, לפעולה ול
mimetype של קישורי העומק באפליקציה.
אפשר לספק ערך deeplink
ספציפי יותר לכל קישור עומק בנפרד
יעד באמצעות
deepLink()
DSL. הפונקציה הזו מקבלת NavDeepLink
שמכיל String
שמייצג את דפוס ה-URI, פרמטר String
שמייצג את פעולות Intent,
String
מייצג את mimeType .
לדוגמה:
deepLink {
uriPattern = "http://www.example.com/plants/"
action = "android.intent.action.MY_ACTION"
mimeType = "image/*"
}
אין הגבלה על מספר קישורי העומק שאפשר להוסיף. בכל פעם שמתקשרים
deepLink()
קישור עומק חדש מצורף לרשימה שנשמרה עבור היעד הזה.
תרחיש מורכב יותר של קישורי עומק מרומזים שמגדיר גם נתיב בטבלה הבאה מוצגים פרמטרים מבוססי-שאילתות:
val baseUri = "http://www.example.com/plants"
fragment<PlantDetailFragment>(nav_routes.plant_detail) {
label = getString(R.string.plant_details_title)
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}"
})
deepLink(navDeepLink {
uriPattern = "${baseUri}/{id}?name={plant_name}"
})
}
אפשר להשתמש אינטרפולציה של מחרוזות כדי לפשט את ההגדרה.
מגבלות
הפלאגין Safe Args הוא
לא תואם ל-Kotlin DSL, כי הפלאגין מחפש קובצי משאבים של XML כדי
ליצור Directions
ו-Arguments
מחלקות.
מידע נוסף
כדאי לקרוא את המאמר בטיחות סוג הניווט. כדי ללמוד איך לספק בטיחות סוג ל-Kotlin DSL קוד לכתיבה של ניווט.