Kotlin DSL ব্যবহার করে প্রোগ্রাম্যাটিকভাবে একটি গ্রাফ তৈরি করুন

নেভিগেশন উপাদান একটি কোটলিন-ভিত্তিক ডোমেন-নির্দিষ্ট ভাষা, বা ডিএসএল প্রদান করে, যা কোটলিনের টাইপ-সেফ নির্মাতাদের উপর নির্ভর করে। এই API আপনাকে XML রিসোর্সের পরিবর্তে আপনার Kotlin কোডে ঘোষণামূলকভাবে আপনার গ্রাফ রচনা করতে দেয়। আপনি যদি আপনার অ্যাপের নেভিগেশন গতিশীলভাবে তৈরি করতে চান তবে এটি কার্যকর হতে পারে। উদাহরণস্বরূপ, আপনার অ্যাপটি একটি বাহ্যিক ওয়েব পরিষেবা থেকে একটি নেভিগেশন কনফিগারেশন ডাউনলোড এবং ক্যাশে করতে পারে এবং তারপরে আপনার কার্যকলাপের onCreate() ফাংশনে গতিশীলভাবে একটি নেভিগেশন গ্রাফ তৈরি করতে সেই কনফিগারেশনটি ব্যবহার করতে পারে।

নির্ভরতা

Kotlin DSL ব্যবহার করতে, আপনার অ্যাপের build.gradle ফাইলে নিম্নলিখিত নির্ভরতা যোগ করুন:

গ্রোভি

dependencies {
    def nav_version = "2.7.7"

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

কোটলিন

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 এ নেভিগেট করতে ব্যবহার করে।

সানফ্লাওয়ার অ্যাপের দুটি গন্তব্য রয়েছে এবং একটি অ্যাকশন রয়েছে যা তাদের সংযুক্ত করে।
চিত্র 1. সূর্যমুখী অ্যাপটিতে দুটি গন্তব্য রয়েছে, home এবং plant_detail , একটি ক্রিয়া সহ যা তাদের সংযুক্ত করে।

একটি Kotlin DSL Nav গ্রাফ হোস্টিং

আপনি আপনার অ্যাপের নেভিগেশন গ্রাফ তৈরি করার আগে, গ্রাফটি হোস্ট করার জন্য আপনার একটি জায়গা প্রয়োজন৷ এই উদাহরণটি টুকরা ব্যবহার করে, তাই এটি একটি FragmentContainerView এর ভিতরে একটি NavHostFragment এ গ্রাফটি হোস্ট করে:

<!-- 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 ব্যবহার করার সময় কর্মের কোন ধারণা নেই।

পরবর্তী ধাপ হল কিছু ধ্রুবক সংজ্ঞায়িত করা যা আপনি আপনার গ্রাফ সংজ্ঞায়িত করার সময় ব্যবহার করবেন।

আপনার গ্রাফের জন্য ধ্রুবক তৈরি করুন

XML-ভিত্তিক নেভিগেশন গ্রাফগুলি অ্যান্ড্রয়েড বিল্ড প্রক্রিয়ার অংশ হিসাবে পার্স করা হয়৷ গ্রাফে সংজ্ঞায়িত প্রতিটি id অ্যাট্রিবিউটের জন্য একটি সাংখ্যিক ধ্রুবক তৈরি করা হয়। রানটাইমে আপনার নেভিগেশন গ্রাফ তৈরি করার সময় এই বিল্ড টাইম জেনারেট করা স্ট্যাটিক আইডি পাওয়া যায় না তাই নেভিগেশন ডিএসএল আইডির পরিবর্তে রুট স্ট্রিং ব্যবহার করে। প্রতিটি রুট একটি অনন্য স্ট্রিং দ্বারা প্রতিনিধিত্ব করা হয় এবং টাইপো-সম্পর্কিত বাগগুলির ঝুঁকি কমাতে এগুলিকে ধ্রুবক হিসাবে সংজ্ঞায়িত করা ভাল অনুশীলন।

আর্গুমেন্ট নিয়ে কাজ করার সময়, এগুলি রুট স্ট্রিং-এর মধ্যে তৈরি করা হয়। রুটে এই যুক্তি তৈরি করা আবারও, টাইপো-সম্পর্কিত বাগগুলির ঝুঁকি কমাতে পারে৷

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

এই উদাহরণে, ট্রেলিং ল্যাম্বডা fragment() ডিএসএল বিল্ডার ফাংশন ব্যবহার করে দুটি খণ্ড গন্তব্য সংজ্ঞায়িত করে। এই ফাংশনের জন্য গন্তব্যের জন্য একটি রুট স্ট্রিং প্রয়োজন যা ধ্রুবক থেকে প্রাপ্ত হয়। ফাংশনটি অতিরিক্ত কনফিগারেশনের জন্য একটি ঐচ্ছিক ল্যাম্বডা গ্রহণ করে, যেমন গন্তব্য লেবেল, সেইসাথে আর্গুমেন্ট এবং গভীর লিঙ্কগুলির জন্য এমবেডেড বিল্ডার ফাংশন।

Fragment ক্লাস যা প্রতিটি গন্তব্যের UI পরিচালনা করে কোণ বন্ধনী ( <> ) এর ভিতরে একটি প্যারামিটারাইজড টাইপ হিসাবে পাস করা হয়। XML ব্যবহার করে সংজ্ঞায়িত করা ফ্র্যাগমেন্ট গন্তব্যগুলিতে android:name অ্যাট্রিবিউট সেট করার মতো এটির একই প্রভাব রয়েছে।

অবশেষে, আপনি স্ট্যান্ডার্ড NavController.navigate() কল ব্যবহার করে home থেকে plant_detail এ নেভিগেট করতে পারেন:

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() ডিএসএল ফাংশনটি বাস্তবায়নকারী ফ্র্যাগমেন্ট ক্লাসে প্যারামিটারাইজ করা যেতে পারে এবং এই গন্তব্যে বরাদ্দ করার জন্য একটি অনন্য রুট স্ট্রিং নেয়, তারপরে একটি ল্যাম্বডা যেখানে আপনি আপনার Kotlin DSL গ্রাফ বিভাগে নেভিগেট করার মতো অতিরিক্ত কনফিগারেশন প্রদান করতে পারেন।

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

কার্যকলাপ গন্তব্য

activity() ডিএসএল ফাংশনটি এই গন্তব্যে বরাদ্দ করার জন্য একটি অনন্য রুট স্ট্রিং নেয় তবে কোনও বাস্তবায়নকারী কার্যকলাপ শ্রেণিতে প্যারামিটারাইজ করা হয় না। পরিবর্তে, আপনি একটি ট্রেলিং ল্যাম্বডাতে একটি ঐচ্ছিক activityClass সেট করেছেন। এই নমনীয়তা আপনাকে একটি কার্যকলাপের জন্য একটি কার্যকলাপের গন্তব্য নির্ধারণ করতে দেয় যা একটি অন্তর্নিহিত অভিপ্রায় ব্যবহার করে চালু করা উচিত, যেখানে একটি সুস্পষ্ট কার্যকলাপ শ্রেণির অর্থ হবে না। টুকরো গন্তব্যগুলির মতো, আপনি একটি লেবেল, আর্গুমেন্ট এবং গভীর লিঙ্কগুলিও কনফিগার করতে পারেন।

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

   activityClass = ActivityDestination::class
}

navigation() ডিএসএল ফাংশনটি নেস্টেড নেভিগেশন গ্রাফ তৈরি করতে ব্যবহার করা যেতে পারে। এই ফাংশনটি তিনটি আর্গুমেন্ট নেয়: গ্রাফে বরাদ্দ করার জন্য একটি রুট, গ্রাফের শুরুর গন্তব্যের রুট এবং গ্রাফটিকে আরও কনফিগার করার জন্য একটি ল্যাম্বডা। বৈধ উপাদানগুলির মধ্যে রয়েছে অন্যান্য গন্তব্য, আর্গুমেন্ট, গভীর লিঙ্ক এবং গন্তব্যের জন্য একটি বর্ণনামূলক লেবেলনেভিগেশনইউআই ব্যবহার করে UI উপাদানগুলিতে নেভিগেশন গ্রাফ বাঁধাই করার জন্য এই লেবেলটি কার্যকর হতে পারে

navigation("route_to_this_graph", nav_routes.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
}

গন্তব্য আর্গুমেন্ট প্রদান

যেকোন গন্তব্য যুক্তি সংজ্ঞায়িত করতে পারে যা ঐচ্ছিক বা প্রয়োজনীয়। NavDestinationBuilderargument() ফাংশন ব্যবহার করে অ্যাকশনগুলিকে সংজ্ঞায়িত করা যেতে পারে, যা সমস্ত গন্তব্য বিল্ডার প্রকারের জন্য বেস ক্লাস। এই ফাংশনটি আর্গুমেন্টের নামটিকে একটি স্ট্রিং এবং একটি ল্যাম্বডা হিসাবে নেয় যা একটি NavArgument তৈরি এবং কনফিগার করতে ব্যবহৃত হয়।

ল্যাম্বডার ভিতরে আপনি আর্গুমেন্ট ডেটা টাইপ, প্রযোজ্য হলে একটি ডিফল্ট মান এবং এটি বাতিলযোগ্য কিনা তা নির্দিষ্ট করতে পারেন।

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 ক্লাস প্রদান করে, আপনি নিয়ন্ত্রণ করতে পারেন ঠিক কিভাবে আপনার টাইপ একটি রুট বা গভীর লিঙ্ক থেকে পার্স করা হয়। এটি আপনাকে আপনার কাস্টম প্রকারের প্রতিফলনহীন এনকোডিং এবং ডিকোডিং প্রদান করতে কোটলিন সিরিয়ালাইজেশন বা অন্যান্য লাইব্রেরি ব্যবহার করতে দেয়।

উদাহরণ স্বরূপ, আপনার সার্চ স্ক্রিনে পাস করা সার্চ প্যারামিটারের প্রতিনিধিত্ব করে এমন একটি ডেটা ক্লাস 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)
  }
}

এটি তখন আপনার কোটলিন ডিএসএল-এ অন্য যেকোন প্রকারের মতো ব্যবহার করা যেতে পারে:

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 চালিত নেভিগেশন গ্রাফের সাথে করতে পারে। একটি গন্তব্যের জন্য একটি গভীর লিঙ্ক তৈরিতে সংজ্ঞায়িত একই পদ্ধতিগুলি কোটলিন ডিএসএল ব্যবহার করে একটি স্পষ্ট গভীর লিঙ্ক তৈরি করার প্রক্রিয়াতে প্রযোজ্য।

যদিও একটি অন্তর্নিহিত গভীর লিঙ্ক তৈরি করার সময়, আপনার কাছে একটি XML নেভিগেশন সংস্থান নেই যা <deepLink> উপাদানগুলির জন্য বিশ্লেষণ করা যেতে পারে। অতএব, আপনি আপনার AndroidManifest.xml ফাইলে একটি <nav-graph> উপাদান রাখার উপর নির্ভর করতে পারবেন না এবং পরিবর্তে আপনার কার্যকলাপে ম্যানুয়ালি উদ্দেশ্য ফিল্টার যোগ করতে হবে। আপনার সরবরাহ করা অভিপ্রায় ফিল্টারটি আপনার অ্যাপের গভীর লিঙ্কগুলির বেস URL প্যাটার্ন, অ্যাকশন এবং মাইমেটাইপের সাথে মেলে।

আপনি deepLink() DSL ফাংশন ব্যবহার করে প্রতিটি পৃথকভাবে গভীর লিঙ্কযুক্ত গন্তব্যের জন্য আরও নির্দিষ্ট deeplink সরবরাহ করতে পারেন। এই ফাংশনটি একটি NavDeepLink গ্রহণ করে যেটিতে URI প্যাটার্নের প্রতিনিধিত্বকারী একটি String , অভিপ্রায় ক্রিয়াগুলিকে প্রতিনিধিত্ব করে একটি String এবং mimeType প্রতিনিধিত্বকারী একটি String রয়েছে।

উদাহরণ স্বরূপ:

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

আপনি সংজ্ঞা সহজ করার জন্য স্ট্রিং ইন্টারপোলেশন ব্যবহার করতে পারেন।

সীমাবদ্ধতা

সেফ আর্গস প্লাগইনটি কোটলিন ডিএসএল-এর সাথে বেমানান, কারণ প্লাগইনটি Directions এবং Arguments ক্লাস তৈরি করার জন্য XML রিসোর্স ফাইলের সন্ধান করে।

আরও জানুন

আপনার Kotlin DSL এবং নেভিগেশন কম্পোজ কোডের জন্য কীভাবে টাইপ নিরাপত্তা প্রদান করবেন তা জানতে নেভিগেশন টাইপ নিরাপত্তা পৃষ্ঠাটি দেখুন।