يوفّر مكوّن التنقّل لغة خاصة بالنطاق مستندة إلى Kotlin، أو
DSL، تعتمد على أدوات الإنشاء الآمنة من حيث النوع في Kotlin. تتيح لك واجهة برمجة التطبيقات هذه إنشاء الرسم البياني بشكل تعريفي في رمز Kotlin البرمجي، بدلاً من داخل مورد XML. قد يكون ذلك مفيدًا إذا كنت تريد إنشاء ميزة التنقّل في تطبيقك بشكل ديناميكي. على سبيل المثال، يمكن لتطبيقك تنزيل إعدادات التنقّل وتخزينها مؤقتًا من خدمة ويب خارجية، ثم استخدام هذه الإعدادات لإنشاء رسم بياني للتنقّل بشكل ديناميكي في دالة onCreate() الخاصة بنشاطك.
الطلبات التابعة
لاستخدام لغة Kotlin DSL مع مكوّنات Fragments، أضِف التبعية التالية إلى ملف build.gradle في تطبيقك:
Groovy
dependencies { def nav_version = "2.10.2" api "androidx.navigation:navigation-fragment-ktx:$nav_version" }
Kotlin
dependencies { val nav_version = "2.10.2" api("androidx.navigation:navigation-fragment-ktx:$nav_version") }
إنشاء رسم بياني
في ما يلي مثال أساسي يستند إلى تطبيق Sunflower. في هذا المثال، لدينا وجهتان: home وplant_detail. تكون وجهة home
متاحة عندما يشغّل المستخدم التطبيق لأول مرة. تعرض هذه الوجهة
قائمة بالنباتات من حديقة المستخدم. عندما يختار المستخدم أحد النباتات، ينتقل التطبيق إلى وجهة plant_detail.
يعرض الشكل 1 هذه الوجهات مع الوسيطات المطلوبة من قِبل الوجهة plant_detail والإجراء to_plant_detail الذي يستخدمه التطبيق للانتقال من home إلى plant_detail.
home وplant_detail، بالإضافة إلى إجراء
يربط بينهما.استضافة رسم بياني للتنقّل بلغة 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، يربط الإجراء معرّف الوجهة بوسيطة واحدة أو أكثر. ومع ذلك، عند استخدام Navigation DSL، يمكن أن يحتوي المسار على وسيطات كجزء من المسار. وهذا يعني أنّه لا يوجد مفهوم للإجراءات عند استخدام لغة DSL.
الخطوة التالية هي تحديد المسارات التي ستستخدمها عند تحديد الرسم البياني.
إنشاء مسارات للرسم البياني
يتم تحليل الرسومات البيانية للتنقّل المستندة إلى XML كجزء
من عملية إنشاء Android. يتم إنشاء ثابت رقمي لكل سمة id
محددة في الرسم البياني. لا تتوفّر هذه المعرّفات الثابتة التي يتم إنشاؤها في وقت الإنشاء عند إنشاء الرسم البياني للتنقّل في وقت التشغيل، لذا تستخدم لغة DSL للتنقّل الأنواع القابلة للتسلسل بدلاً من المعرّفات. يتم تمثيل كل مسار بنوع فريد.
عند التعامل مع الوسيطات، يتم دمجها في نوع المسار. يتيح لك ذلك منع أخطاء الكتابة في وسيطات التنقّل.
@Serializable data object Home
@Serializable data class Plant(val id: String)
إنشاء رسم بياني باستخدام NavGraphBuilder DSL
بعد تحديد مساراتك، يمكنك إنشاء الرسم البياني للتنقّل.
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)
}
}
في هذا المثال، يتم تحديد وجهتَين مجزأتَين باستخدام دالة إنشاء DSL
fragment(). تتطلّب هذه الدالة وسيطتَي نوع.
أولاً، فئة Fragment التي توفّر واجهة المستخدم لهذه الوجهة يؤدي ضبط هذا الإعداد إلى التأثير نفسه الذي يحدث عند ضبط السمة android:name في وجهات الأجزاء المحدّدة باستخدام XML.
ثانيًا، المسار. يجب أن يكون هذا النوع قابلاً للتسلسل ويمتد من Any. يجب أن يحتوي على أي وسيطات تنقّل سيتم استخدامها من خلال هذه الوجهة وأنواعها.
تقبل الدالة أيضًا دالة lambda اختيارية لضبط إعدادات إضافية، مثل تصنيف الوجهة، بالإضافة إلى دوال الإنشاء المضمّنة للوسيطات المخصّصة والروابط لصفحات في التطبيق.
التنقّل باستخدام رسم Kotlin DSL البياني
أخيرًا، يمكنك الانتقال من 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، ولكل منها دالة إضافة مضمّنة خاصة بها متاحة لإنشاء الوجهة وضبطها.
وجهات الأجزاء
يمكن تحديد مَعلمات دالة DSL fragment() باستخدام فئة الجزء لواجهة المستخدم ونوع المسار المستخدَم لتحديد هذه الوجهة بشكل فريد، متبوعًا بدالة lambda حيث يمكنك تقديم إعدادات إضافية كما هو موضّح في قسم التنقّل باستخدام رسم Kotlin DSL البياني.
fragment<MyFragment, MyRoute> {
label = getString(R.string.fragment_title)
// custom argument types, deepLinks
}
وجهة النشاط
تتطلّب دالة DSL activity() معلَمة نوع للمسار، ولكن لا يتم تحديد معلَماتها لأي فئة نشاط تنفّذها. بدلاً من ذلك، يمكنك ضبط activityClass اختياري في
دالة lambda لاحقة. تتيح لك هذه المرونة تحديد وجهة نشاط لنشاط يجب تشغيله باستخدام النية الضمنية، حيث لا يكون لفئة النشاط الصريحة معنى. كما هو الحال مع وجهات الأجزاء، يمكنك أيضًا
ضبط تصنيف ووسيطات مخصّصة وروابط عميقة.
activity<MyRoute> {
label = getString(R.string.activity_title)
// custom argument types, deepLinks...
activityClass = MyActivity::class
}
وجهة الرسم البياني للتنقّل
يمكن استخدام دالة DSL navigation() لإنشاء رسم بياني للتنقّل
المتداخل. تأخذ هذه الدالة مَعلمة نوع
للمسار الذي سيتم تعيينه لهذا الرسم البياني. وتتطلّب أيضًا وسيطتَين:
مسار الوجهة الأولى في الرسم البياني، ودالة lambda لضبط
الرسم البياني بشكل أكبر. تشمل العناصر الصالحة الوجهات الأخرى وأنواع الوسيطات المخصّصة والروابط لصفحات معيّنة وتصنيفًا وصفيًا للوجهة.
يمكن أن يكون هذا التصنيف مفيدًا لربط الرسم البياني للتنقّل بمكوّنات واجهة المستخدم باستخدام
NavigationUI.
@Serializable data object HomeGraph
@Serializable data object Home
navigation<HomeGraph>(startDestination = 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
}
تقديم وسيطات الوجهة
يمكن تحديد وسيطات الوجهة كجزء من فئة المسار. ويمكن تحديدها بالطريقة نفسها التي تحدد بها أي فئة Kotlin. يتم تعريف الوسيطات المطلوبة على أنّها أنواع غير قابلة للإلغاء، ويتم تعريف الوسيطات الاختيارية بقيم تلقائية.
The underlying mechanism for representing routes and their arguments is string
based. يتيح استخدام السلاسل لإنشاء نماذج المسارات تخزين حالة التنقّل واستعادتها من القرص أثناء تغييرات الإعدادات وإيقاف العملية نهائيًا من خلال النظام. لهذا السبب، يجب أن تكون كل وسيطة تنقّل قابلة للتسلسل، أي يجب أن تتضمّن طريقة تحوّل التمثيل في الذاكرة لقيمة الوسيطة إلى String.
يُنشئ المكوّن الإضافي لتسلسل Kotlin
طرق التسلسل تلقائيًا للأنواع
الأساسية عند إضافة التعليق التوضيحي
@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
@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, SearchRoute>(
typeMap = mapOf(typeOf<SearchParameters>() to SearchParametersType)
) {
label = getString(R.string.plant_search_title)
}
عند الانتقال إلى الوجهة، أنشئ مثيلاً لمسارك:
val params = SearchParameters("rose", listOf("available"))
navController.navigate(route = SearchRoute(params))
يمكن الحصول على المَعلمة من المسار في الوجهة:
val searchRoute = navController().getBackStackEntry<SearchRoute>().toRoute<SearchRoute>()
val params = searchRoute.parameters
الروابط العميقة
يمكن إضافة الروابط لصفحات في التطبيق إلى أي وجهة، تمامًا كما هو الحال مع الرسم البياني للتنقّل المستند إلى XML. تنطبق جميع الإجراءات نفسها المحدّدة في إنشاء رابط عميق لوجهة على عملية إنشاء رابط عميق باستخدام Kotlin DSL.
عند إنشاء رابط عميق ضمني، لن يكون لديك مورد تنقّل بتنسيق XML يمكن تحليله بحثًا عن عناصر <deepLink>. لذلك، لا يمكنك الاعتماد على وضع عنصر <nav-graph> في ملف AndroidManifest.xml، وعليك بدلاً من ذلك إضافة فلاتر intent إلى نشاطك يدويًا. يجب أن يتطابق فلتر الأهداف الذي تقدّمه مع المسار الأساسي والإجراء ونوع MIME للروابط لصفحات معيّنة في تطبيقك.
تتم إضافة الروابط العميقة إلى وجهة من خلال استدعاء الدالة deepLink داخل دالة lambda الخاصة بالوجهة. وتقبل المسار كنوع ذي معلَمات، ومعلَمة basePath للمسار الأساسي لعنوان URL المستخدَم للرابط لصفحة معيّنة.
يمكنك أيضًا إضافة إجراء ونوع MIME باستخدام دالة lambda اللاحقة
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}) - يتم إلحاق المَعلمات التي لها قيمة تلقائية (المَعلمات الاختيارية) على شكل مَعلمات طلب بحث (مثال:
?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.