يوفر مكوِّن التنقل لغة خاصة بالنطاق تستند إلى Kotlin أو DSL والتي تعتمد على أدوات الإنشاء المناسبة للنوع في Kotlin.
تسمح لك واجهة برمجة التطبيقات هذه بإنشاء الرسم البياني في كود 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
.
استضافة الرسم البياني 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
كجزء من عملية تصميم Android. يتم إنشاء ثابت رقمي لكل سمة id
محدّدة في الرسم البياني. لا تتوفر أرقام التعريف الثابتة التي تم إنشاؤها في وقت الإنشاء عند إنشاء رسم بياني للتنقل في وقت التشغيل، لذا تستخدم 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"
}
إنشاء رسم بياني باستخدام NavGraphBuilder 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 اللاحقة وجهتين للأجزاء باستخدام دالة إنشاء DSL fragment()
. تتطلب هذه الدالة سلسلة مسار للوجهة
التي يتم الحصول عليها من الثوابت. تقبل الدالة أيضًا دالة lambda الاختيارية
في إعداد المزيد من الإعدادات، مثل تصنيف الوجهة، وكذلك
دوال أداة الإنشاء المضمّنة للوسيطات والروابط لصفحة معيّنة.
يتم تمرير الفئة Fragment
التي تدير واجهة المستخدم لكل وجهة كنوع مَعلمة داخل أقواس زاوية (<>
). ويكون لذلك التأثير نفسه الناتج عن ضبط السمة android:name
على وجهات الأجزاء المحدّدة باستخدام XML.
التنقّل باستخدام الرسم البياني لـ Kotlin DSL
وأخيرًا، يمكنك الانتقال من 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 وفقًا لفئة الجزء المُنفَّذ
fragment<FragmentDestination>(nav_routes.route_name) {
label = getString(R.string.fragment_title)
// arguments, deepLinks
}
وجهة النشاط
تستخدم دالة DSL activity()
سلسلة مسار فريدة لتعيينها لهذه الوجهة، ولكنها لا تتضمن معلمة لأي فئة نشاط يتم تنفيذها. بدلاً من ذلك، يمكنك إعداد activityClass
اختيارية في دالة lambda لاحقة. تتيح لك هذه المرونة تحديد وجهة نشاط لنشاط يجب إطلاقه باستخدام هدف ضمني، حيث لا تكون فئة النشاط الفاضحة مفهومة. وكما هو الحال مع وجهات الأجزاء، يمكنك
أيضًا ضبط تصنيف ووسيطات وروابط لصفحات في التطبيق.
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)
بدلاً من ذلك، يمكنك أيضًا استخدام عامل التشغيل الأحادي لإضافة وجهة تم إنشاؤها حديثًا إلى الرسم البياني مباشرةً:
// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
route = Graph.CustomDestination.route
}
تقديم وسيطات الوجهة
يمكن أن تحدد أي وجهة الوسيطات الاختيارية أو المطلوبة. يمكن تحديد الإجراءات باستخدام دالة argument()
على NavDestinationBuilder
، وهي الفئة الأساسية لجميع أنواع أدوات إنشاء الوجهات. تأخذ هذه الدالة اسم الوسيطة كسلسلة
ودالة lambda التي يتم استخدامها لإنشاء
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)
روابط لصفحات معيّنة
يمكن إضافة روابط لمواضع معينة إلى أي وجهة، تمامًا كما هو الحال مع الرسم البياني للتنقل المستند إلى XML. إنّ جميع الإجراءات نفسها المحدّدة في إنشاء رابط لصفحة معيّنة لوجهة معيّنة تنطبق على عملية إنشاء رابط صريح لصفحة في التطبيق باستخدام Kotlin DSL.
ولكن عند إنشاء رابط ضمني لصفحة في التطبيق، لن يتوفّر لديك مورد تنقّل بتنسيق XML يمكن تحليله لعناصر <deepLink>
. وبالتالي، لا يمكنك الاعتماد على وضع عنصر <nav-graph>
في ملف AndroidManifest.xml
وعليك بدلاً من ذلك إضافة فلاتر حسب النية بالشراء إلى نشاطك يدويًا.
ويجب أن يتطابق فلتر الأهداف الذي تقدِّمه مع نمط عنوان URL الأساسي والإجراء ونوع Mime للروابط المؤدية إلى صفحات في تطبيقك.
يمكنك توفير 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}"
})
}
يمكنك استخدام استكمال السلسلة لتبسيط التعريف.
القيود
إنّ المكوّن الإضافي فارغات الأمان غير متوافق مع لغة Kotlin DSL، لأنّ المكوّن الإضافي يبحث عن ملفات موارد XML لإنشاء فئتَين Directions
وArguments
.
مزيد من المعلومات
يمكنك الاطّلاع على صفحة أمان نوع التنقل للتعرّف على كيفية توفير أمان الكتابة لرمز Kotlin DSL ورمز التنقل Compose.