Fragments و Kotlin DSL

مؤلفه Navigation یک زبان دامنه خاص مبتنی بر Kotlin یا DSL را ارائه می دهد که به سازندگان نوع ایمن Kotlin متکی است. این API به شما این امکان را می‌دهد که نمودار خود را به‌جای یک منبع XML، در کد Kotlin خود به صورت اعلامی بنویسید. اگر می خواهید ناوبری برنامه خود را به صورت پویا بسازید، می تواند مفید باشد. به عنوان مثال، برنامه شما می‌تواند یک پیکربندی پیمایش را از یک سرویس وب خارجی دانلود و ذخیره کند و سپس از آن پیکربندی برای ایجاد پویا یک نمودار ناوبری در عملکرد onCreate() فعالیت شما استفاده کند.

وابستگی ها

برای استفاده از Kotlin DSL با Fragments، وابستگی زیر را به فایل build.gradle برنامه خود اضافه کنید:

شیار

dependencies {
    def nav_version = "2.8.2"

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

کاتلین

dependencies {
    val nav_version = "2.8.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 استفاده می‌کند.

برنامه Sunflower دارای دو مقصد به همراه یک عمل است که آنها را به هم متصل می کند.
شکل 1. برنامه Sunflower دو مقصد دارد، home و plant_detail ، به همراه یک عمل که آنها را به هم متصل می کند.

میزبانی کاتلین DSL Nav Graph

قبل از اینکه بتوانید نمودار ناوبری برنامه خود را بسازید، به مکانی برای میزبانی نمودار نیاز دارید. این مثال از قطعات استفاده می کند، بنابراین گراف را در 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 به عنوان بخشی از فرآیند ساخت اندروید تجزیه می شوند. یک ثابت عددی برای هر ویژگی id تعریف شده در نمودار ایجاد می شود. این شناسه‌های استاتیک تولید شده در زمان ساخت، هنگام ساخت نمودار ناوبری شما در زمان اجرا در دسترس نیستند، بنابراین Navigation 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() DSL builder تعریف شده است. این تابع به دو نوع آرگومان نیاز دارد.

ابتدا یک کلاس Fragment که رابط کاربری این مقصد را ارائه می کند. تنظیم این همان اثر تنظیم ویژگی android:name در مقصدهای قطعه ای است که با استفاده از XML تعریف شده اند.

دوم، مسیر. این باید یک نوع سریال‌سازی باشد که از Any گسترش می‌یابد. باید حاوی هر گونه آرگومان ناوبری باشد که توسط این مقصد استفاده می شود و انواع آنها.

این تابع همچنین یک لامبدا اختیاری را برای پیکربندی اضافی می پذیرد، مانند برچسب مقصد، و همچنین توابع سازنده تعبیه شده برای آرگومان های سفارشی و پیوندهای عمیق.

در نهایت، می توانید با استفاده از فراخوانی NavController.navigate() از home به plant_detail پیمایش کنید:

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 که در آن می‌توانید پیکربندی اضافی را همانطور که در بخش Navigating with Kotlin DSL graph توضیح داده شده است، ارائه دهید.

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

مقصد فعالیت

تابع DSL activity() یک پارامتر نوع را برای مسیر می گیرد اما به هیچ کلاس اکتیویتی پیاده سازی پارامتر نمی شود. در عوض، یک activityClass اختیاری را در یک لامبدای دنباله دار تنظیم می کنید. این انعطاف‌پذیری به شما امکان می‌دهد یک مقصد فعالیت را برای یک فعالیت تعریف کنید که باید با استفاده از یک هدف ضمنی راه‌اندازی شود، جایی که کلاس فعالیت صریح معنا ندارد. همانند مقصدهای قطعه، می توانید یک برچسب، آرگومان های سفارشی و پیوندهای عمیق را نیز پیکربندی کنید.

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

   activityClass = MyActivity::class
}

تابع navigation() DSL را می توان برای ساخت یک نمودار ناوبری تودرتو استفاده کرد. این تابع یک پارامتر نوع برای مسیر تعیین می کند تا به این نمودار اختصاص یابد. همچنین دو آرگومان نیاز دارد: مسیر مقصد شروع گراف، و یک لامبدا برای پیکربندی بیشتر نمودار. عناصر معتبر شامل مقصدهای دیگر، انواع آرگومان سفارشی، پیوندهای عمیق، و یک برچسب توصیفی برای مقصد هستند . این برچسب می تواند برای اتصال نمودار پیمایش به اجزای رابط کاربری با استفاده از NavigationUI مفید باشد.

@Serializable data object HomeGraph
@Serializable data object Home

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

پشتیبانی از مقاصد سفارشی

اگر از نوع مقصد جدیدی استفاده می‌کنید که مستقیماً از Kotlin DSL پشتیبانی نمی‌کند، می‌توانید این مقصدها را با استفاده از addDestination() به Kotlin DSL خود اضافه کنید:

// 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 تعریف می کنید، تعریف کرد. آرگومان های مورد نیاز به عنوان انواع غیر قابل تهی و آرگومان های اختیاری با مقادیر پیش فرض تعریف می شوند.

مکانیسم اساسی برای نمایش مسیرها و آرگومان های آنها مبتنی بر رشته است. استفاده از رشته‌ها برای مدل‌سازی مسیرها اجازه می‌دهد تا وضعیت ناوبری در طول تغییرات پیکربندی و مرگ فرآیند آغاز شده توسط سیستم ذخیره و از دیسک بازیابی شود. به همین دلیل، هر آرگومان ناوبری باید سریال‌پذیر باشد، یعنی باید متدی داشته باشد که نمایش درون حافظه مقدار آرگومان را به یک 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
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

پیوندهای عمیق

پیوندهای عمیق را می توان به هر مقصدی اضافه کرد، همانطور که می توانند با یک نمودار ناوبری مبتنی بر XML. همه همان رویه‌های تعریف شده در ایجاد پیوند عمیق برای مقصد، در فرآیند ایجاد پیوند عمیق با استفاده از Kotlin DSL اعمال می‌شود.

با این حال، هنگام ایجاد یک پیوند عمیق ضمنی ، منبع ناوبری XML ندارید که بتوان آن را برای عناصر <deepLink> تجزیه و تحلیل کرد. بنابراین، نمی‌توانید به قرار دادن عنصر <nav-graph> در فایل AndroidManifest.xml خود تکیه کنید و در عوض باید فیلترهای هدف را به صورت دستی به فعالیت خود اضافه کنید. فیلتر قصدی که ارائه می‌کنید باید با مسیر اصلی، عملکرد و نوع mime پیوندهای عمیق برنامه شما مطابقت داشته باشد.

پیوندهای عمیق با فراخوانی تابع deepLink در داخل لامبدا مقصد به مقصد اضافه می شوند. مسیر را به عنوان یک نوع پارامتری شده و پارامتر basePath برای مسیر پایه URL مورد استفاده برای پیوند عمیق می پذیرد.

همچنین می‌توانید با استفاده از لامبدا انتهایی deepLinkBuilder یک اقدام و mimetype اضافه کنید.

مثال زیر یک 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 می گردد.