ফ্র্যাগমেন্ট ও Kotlin DSL

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

ডিপেন্ডেন্সি

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

গ্রুভি

dependencies {
    def nav_version = "2.10.2"

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

কোটলিন

dependencies {
    val nav_version = "2.10.2"

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

গ্রাফ তৈরি করা হচ্ছে

Sunflower অ্যাপ-এর উপর ভিত্তি করে এখানে একটি প্রাথমিক উদাহরণ দেওয়া হল। এই উদাহরণের জন্য, আমাদের দুটি গন্তব্য আছে: home এবং plant_detail। ব্যবহারকারী প্রথমবার অ্যাপ লঞ্চ করলে home ডেস্টিনেশন দেখা যায়। এই ডেস্টিনেশন ব্যবহারকারীর বাগানের গাছের তালিকা দেখায়। ব্যবহারকারী কোনও একটি প্ল্যান্ট বেছে নিলে, অ্যাপটি plant_detail গন্তব্যে নেভিগেট করে।

ছবি ১-এ এইসব গন্তব্য দেখানো হয়েছে, এর সাথে plant_detail গন্তব্যের জন্য প্রয়োজনীয় আর্গুমেন্ট এবং to_plant_detail অ্যাকশন দেখানো হয়েছে, যা অ্যাপটি home থেকে plant_detail-এ নেভিগেট করার জন্য ব্যবহার করে।

Sunflower অ্যাপে দুটি গন্তব্য এবং সেগুলিকে কানেক্ট করে এমন একটি অ্যাকশন
            রয়েছে।
ছবি ১. Sunflower অ্যাপে দুটি ডেস্টিনেশন আছে, home এবং plant_detail, এর সাথে একটি অ্যাকশন আছে যা এগুলিকে কানেক্ট করে।

Kotlin DSL Nav Graph হোস্ট করা

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

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

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

Android বিল্ড প্রসেসের অংশ হিসেবে XML-ভিত্তিক নেভিগেশন গ্রাফ পার্স করা হয়। গ্রাফে সংজ্ঞায়িত প্রতিটি id অ্যাট্রিবিউটের জন্য একটি সংখ্যাসূচক ধ্রুবক তৈরি করা হয়। বিল্ড করার সময় জেনারেট হওয়া এইসব স্ট্যাটিক আইডি রানটাইমে আপনার নেভিগেশন গ্রাফ বিল্ড করার সময় উপলভ্য থাকে না, তাই নেভিগেশন 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 বিল্ডার ফাংশন ব্যবহার করে দুটি ফ্র্যাগমেন্ট ডেস্টিনেশন সংজ্ঞায়িত করা হয়েছে। এই ফাংশনের জন্য দুটি টাইপ আর্গুমেন্ট প্রয়োজন। .

প্রথমে, একটি Fragment ক্লাস যা এই গন্তব্যের জন্য UI প্রদান করে। এটি সেট করলে, XML ব্যবহার করে সংজ্ঞায়িত ফ্র্যাগমেন্ট ডেস্টিনেশনে android:name অ্যাট্রিবিউট সেট করার মতোই একই প্রভাব পড়ে।

দ্বিতীয়ত, রুট। এটি অবশ্যই একটি সিরিয়ালাইজেবল টাইপ হতে হবে যা Any থেকে প্রসারিত হয়। এতে এই গন্তব্যের দ্বারা ব্যবহার করা হবে এমন যেকোনও নেভিগেশন আর্গুমেন্ট এবং তাদের ধরন থাকতে হবে।

এছাড়াও, ফাংশনটি অতিরিক্ত কনফিগারেশনের জন্য একটি ঐচ্ছিক ল্যাম্বডা গ্রহণ করে, যেমন গন্তব্য লেবেল, সেইসাথে কাস্টম আর্গুমেন্ট এবং ডিপ লিঙ্কের জন্য এম্বেড করা বিল্ডার ফাংশন।

অবশেষে, আপনি 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 ডেস্টিনেশন, যার প্রত্যেকটির নিজস্ব ইনলাইন এক্সটেনশন ফাংশন আছে যা ডেস্টিনেশন তৈরি ও কনফিগার করার জন্য উপলভ্য।

ফ্র্যাগমেন্ট ডেস্টিনেশন

fragment() DSL ফাংশন UI-এর জন্য ফ্র্যাগমেন্ট ক্লাস এবং এই গন্তব্য অনন্যভাবে শনাক্ত করার জন্য ব্যবহৃত রুটের ধরন দিয়ে প্যারামিটারাইজ করা যেতে পারে, এর পরে একটি ল্যাম্বডা থাকে যেখানে আপনি আপনার Kotlin DSL গ্রাফের মাধ্যমে নেভিগেট করা বিভাগে বর্ণিত অতিরিক্ত কনফিগারেশন প্রদান করতে পারেন।

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

অ্যাক্টিভিটি ডেস্টিনেশন

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

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

   activityClass = MyActivity::class
}

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

@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)

বিকল্প হিসেবে, আপনি গ্রাফে সরাসরি নতুন তৈরি করা ডেস্টিনেশন যোগ করতে ইউনারি প্লাস অপারেটর ব্যবহার করতে পারেন:

// The NavigatorProvider is retrieved from the NavController
+navigatorProvider[CustomNavigator::class].createDestination().apply {
    route = Graph.CustomDestination.route
}

গন্তব্য আর্গুমেন্ট প্রদান করা হচ্ছে

রুট ক্লাসের অংশ হিসেবে গন্তব্য আর্গুমেন্ট সংজ্ঞায়িত করা যেতে পারে। যেকোনও Kotlin ক্লাসের মতো এগুলিও একইভাবে সংজ্ঞায়িত করা যেতে পারে। প্রয়োজনীয় আর্গুমেন্টকে নন-নালেবেল টাইপ হিসেবে এবং ঐচ্ছিক আর্গুমেন্টকে ডিফল্ট ভ্যালু সহ সংজ্ঞায়িত করা হয়।

রুট এবং তাদের আর্গুমেন্ট দেখানোর জন্য অন্তর্নিহিত মেকানিজম স্ট্রিং ভিত্তিক। রুট মডেল করার জন্য স্ট্রিং ব্যবহার করলে, কনফিগারেশন পরিবর্তন এবং সিস্টেম-ইনিশিয়েটেড প্রসেস বন্ধ করার সময় নেভিগেশন স্টেট ডিস্ক থেকে সেভ ও রিস্টোর করা যায়। এই কারণে, প্রতিটি নেভিগেশন আর্গুমেন্ট সিরিয়ালাইজ করা যেতে পারে, অর্থাৎ, এতে এমন একটি পদ্ধতি থাকতে হবে যা আর্গুমেন্ট ভ্যালুর ইন-মেমরি রিপ্রেজেন্টেশনকে String-এ কনভার্ট করে।

Kotlin serialization প্লাগ-ইন অটোমেটিক বেসিক ধরনের জন্য সিরিয়ালাইজেশন পদ্ধতি জেনারেট করে যখন @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> এলিমেন্টের জন্য বিশ্লেষণ করা যেতে পারে। তাই, আপনার AndroidManifest.xml ফাইলে <nav-graph> এলিমেন্ট যোগ করার উপর নির্ভর করতে পারবেন না এবং এর পরিবর্তে আপনার অ্যাক্টিভিটিতে ম্যানুয়ালি ইনটেন্ট ফিল্টার যোগ করতে হবে। আপনার দেওয়া ইনটেন্ট ফিল্টারটি আপনার অ্যাপের ডিপ লিঙ্কের বেস পাথ, অ্যাকশন এবং মাইমটাইপের সাথে ম্যাচ করতে হবে।

গন্তব্যের ল্যাম্বডার মধ্যে deepLink ফাংশন কল করে গন্তব্যে ডিপ লিঙ্ক যোগ করা হয়। এটি প্যারামিটারাইজড ধরন হিসেবে রুট গ্রহণ করে এবং ডিপ লিঙ্কের জন্য ব্যবহৃত URL-এর বেস পাথের জন্য প্যারামিটার basePath গ্রহণ করে।

এছাড়াও, আপনি deepLinkBuilder ট্রেলিং ল্যাম্বডা ব্যবহার করে অ্যাকশন ও মাইমটাইপ যোগ করতে পারবেন।

নিম্নলিখিত উদাহরণটি Home গন্তব্যের জন্য একটি ডিপ লিঙ্ক URI তৈরি করে।

@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-এর সাথে কাজ করে না, কারণ প্লাগ-ইন Directions এবং Arguments ক্লাস তৈরি করার জন্য XML রিসোর্স ফাইল খোঁজে।