إنشاء رسم بياني آليًا باستخدام لغة Kotlin DSL

يوفر مكوِّن التنقل لغة خاصة بالنطاق تستند إلى Kotlin، DSL التي تعتمد على لغة ترميز Kotlin أدوات إنشاء آمنة من النوع الآمن. تتيح لك واجهة برمجة التطبيقات هذه إنشاء الرسم البياني في كود Kotlin الخاص بك، بدلاً من الدخول إلى مورد XML. يمكن أن يكون هذا مفيدًا إذا كنت ترغب في إنشاء التنقّل في تطبيقك بشكل ديناميكي على سبيل المثال، يمكن لتطبيقك تنزيل تخزين مؤقت لتهيئة التنقل من خدمة ويب خارجية ثم استخدام هذه التهيئة لإنشاء رسم بياني للتنقل بشكل ديناميكي في صفحة onCreate().

التبعيات

لاستخدام Kotlin DSL، أضف التبعية التالية إلى جدول بيانات ملف build.gradle:

Groovy

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")
}

إنشاء رسم بياني

لنبدأ بمثال أساسي يعتمد على تطبيق Sunflower. لهذا الغرض على سبيل المثال، لدينا وجهتان: home وplant_detail. home وجود الوجهة عند تشغيل المستخدم للتطبيق لأول مرة. هذه الوجهة تعرض قائمة بالنباتات من حديقة المستخدم. عندما يحدد المستخدم أحد النباتات، ينتقل التطبيق إلى وجهة plant_detail.

يوضح الشكل 1 هذه الوجهات مع الوسيطات المطلوبة بواسطة وجهة واحدة (plant_detail) وإجراء، to_plant_detail، يستخدمه التطبيق الانتقال من home إلى plant_detail.

يحتوي تطبيق دوار الشمس على وجهتين إلى جانب إجراء
            تربطهما معًا.
الشكل 1. هناك وجهتان لتطبيق دوار الشمس، 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"
}

بمجرد تحديد الثوابت، يمكنك إنشاء شريط التنقل الرسم البياني.

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() دالة إنشاء DSL. تتطلب هذه الدالة سلسلة مسار للوجهة الذي يتم الحصول عليه من الثوابت. تقبل الدالة أيضًا الدالة الاختيارية lambda لمزيد من الإعدادات، مثل تصنيف الوجهة، وكذلك دوال الإنشاء المضمنة للوسيطات والروابط لمواضع معينة.

تتضمن فئة Fragment التي يدير واجهة المستخدم لكل وجهة يتم تمريرها كنوع بمعلمة داخل أقواس معقوفة (<>). يؤدي هذا الإجراء إلى التأثير نفسه الناتج عن ضبط السمة android:name. على وجهات مجزأة يتم تحديدها باستخدام XML.

أخيرًا، يمكنك الانتقال من 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 حيث يمكنك توفير إعدادات إضافية كما هو موضّح في التنقل باستخدام الرسم البياني لـ Kotlin DSL .

fragment<FragmentDestination>(nav_routes.route_name) {
   label = getString(R.string.fragment_title)
   // arguments, deepLinks
}

وجهة النشاط

activity() تستخدم دالة DSL سلسلة مسار فريدة لتعيينها إلى هذه الوجهة، ولكن لم يتم تحديد معلمة إلى أي فئة نشاط تنفيذ. بدلاً من ذلك، يمكنك تعيين اختيارية activityClass في دالة lambda لاحقة. تتيح لك هذه المرونة تحديد وجهة نشاط لنشاط يجب إطلاقه باستخدام نية ضمنية، حيث تكون صف النشاط الصريح لن يكون منطقيًا. وكما هو الحال مع الوجهات المجزأة، يمكنك أيضًا ضبط تصنيف ووسيطات وروابط لصفحات معيّنة.

activity(nav_routes.route_name) {
   label = getString(R.string.activity_title)
   // arguments, deepLinks...

   activityClass = ActivityDestination::class
}

navigation() يمكن استخدام دالة DSL لإنشاء الرسم البياني المتداخل للتنقّل. تستخدم هذه الدالة ثلاث وسيطات: مسار إلى وتعيينه إلى الرسم البياني، ومسار وجهة بداية الرسم البياني، lambda لتهيئة الرسم البياني بشكلٍ أكبر. تتضمن العناصر الصالحة الوجهات الأخرى، والجدالات والروابط لمواضع معينة تصنيفًا وصفيًا للوجهة. يمكن أن يكون هذا التصنيف مفيدًا لربط الرسم البياني للتنقل بواجهة المستخدم. المكونات باستخدام واجهة مستخدم التنقل

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، وهي الفئة الأساسية لجميع أداة إنشاء الوجهات. تأخذ هذه الدالة اسم الوسيطة كسلسلة و 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 الأساسي والإجراء 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}"
   })
}

يمكنك استخدام استكمال السلسلة لتبسيط التعريف.

القيود

المكوّن الإضافي الوسيطات الآمنة هو غير متوافق مع Kotlin DSL، حيث يبحث المكون الإضافي عن ملفات موارد XML إنشاء صفَّين Directions وArguments

مزيد من المعلومات

التحقّق من أمان نوع التنقّل لمعرفة كيفية توفير أمان الكتابة في Kotlin DSL و رمز إنشاء التنقّل.