অ্যাপ ফাউন্ডেশন

Jetpack Navigation3-এর মাধ্যমে টাইপ-সেফ ডিপ লিঙ্ক তৈরি করা

স্পিকার

  • Clara Fok

    সফ্টওয়্যার ইঞ্জিনিয়ার

  • Ian Lake

    ইঞ্জিনিয়ারিং ম্যানেজার

সম্পর্কে

টক সম্পর্কে

মডুলার Android অ্যাপ জুড়ে ডিপ লিঙ্ক ম্যানেজ করার ফলে সহজেই রুট ভেঙে যেতে পারে এবং রানটাইমে অপ্রত্যাশিত সমস্যা দেখা দিতে পারে। এই সেশনে Compose-এর জন্য Jetpack Navigation3-এর নতুন টাইপ-সেফ ডিপ লিঙ্কিং আর্কিটেকচার সম্পর্কে বিস্তারিত আলোচনা করা হয়েছে, যা আপনাকে একটি শক্তিশালী এবং স্কেলেবল রাউটিং সিস্টেম তৈরি করতে সাহায্য করবে। আমরা দেখব, কীভাবে আউট-অফ-দ্য-বক্স ডিপ লিঙ্ক ম্যাচিং API প্রয়োগ করতে হয়, জটিল টাইপ-সেফ আর্গুমেন্টের জন্য কাস্টম সিরিয়ালাইজার লিখতে হয় এবং ডায়নামিক হায়ারার্কিকাল ব্যাক স্ট্যাক তৈরি করতে হয়, যাতে ব্যবহারকারীর নেভিগেশন অনুমান করা যায়।

সারাংশ

  • নেভিগেশন ৩ ডিপ লিঙ্ক API, ইনকামিং ডিপ লিঙ্ক অনুরোধকে নেভিগেশন কীতে পরিবর্তন করে যাতে আপনার প্রাথমিক ব্যাক স্ট্যাক নির্ধারণ করা যায়। KMP-মানানসই DeepLinkRequest ক্লাস ইনপুট এনক্যাপসুলেট করে, যার মধ্যে Android Intent কনস্ট্রাক্টর অন্তর্ভুক্ত।
  • URI প্যাটার্ন ম্যাচ করতে এবং আর্গুমেন্টকে টাইপ-সেফ NavKey প্রপার্টিতে ডিসিরিয়ালাইজ করতে UriDeepLinkMatcher Kotlin সিরিয়ালাইজেশন ব্যবহার করে। প্রিমিটিভ টাইপ অটোমেটিক কাজ করে, ভ্যালু পার্স করা না গেলে ম্যাচিং ব্যর্থ হয়।
  • জটিল ডিপ লিংক স্ট্রিং ম্যানেজ করতে ডেভেলপাররা DeepLinkSerializer এক্সটেন্ড করতে অথবা matchArguments ওভাররাইড করতে পারেন। UriDeepLinkMatcher সাবক্লাস করার মাধ্যমে আপনি Kotlin সিরিয়ালাইজেশন রান করার আগে কাঁচা URI প্লেসহোল্ডার প্রিপ্রসেস করতে পারবেন।
  • ম্যাচারে withBackStack কল করলে সেরা ম্যাচের জন্য সিন্থেটিক ব্যাক স্ট্যাক লেজিলি তৈরি হয়। এটি প্রতিটি সঠিক ম্যাচের জন্য অপ্রয়োজনীয় ব্যাক স্ট্যাক গণনা না করেই সঠিক সিস্টেম ব্যাক আচরণ নিশ্চিত করে।

রিসোর্স

Gemini Notebook

বিষয়

  • jetpack-navigation-3
  • ডিপ-লিঙ্ক
  • kotlin-serialization
  • kotlin-multiplatform
  • android-architecture

ট্রান্সস্ক্রিপ্ট করা

ভূমিকা ও নেভিগেশন ৩টি রেসিপি

  • হ্যালো, আমি ইয়ান লেক, Jetpack আর্কিটেকচার টিমের ইঞ্জিনিয়ারিং ম্যানেজার।

  • আর আমি ক্লারা ফক, Nav 3-এর টেক লিড।

  • আজ আমরা এমন একটি ফিচার নিয়ে কথা বলব যা আমার খুব প্রিয়, ডিপ লিঙ্ক।

  • আসলে, আমি এগুলি এতটাই পছন্দ করি যে Nav3-এর 1.2 রিলিজের অংশ হিসেবে Nav2 এবং Nav3 উভয়ের জন্যই ইমপ্লিমেন্টেশন করেছি।

  • আমার মনে হয় আপনি আসলে এটি তিনবার করেছেন, কারণ আমরা Nav 3 রেসিপিতেও একটি ইমপ্লিমেন্টেশন করেছি।

  • হ্যাঁ, এটি সত্যি।

  • Nav3-এর কম্পোজ-ফার্স্ট অ্যাপ্রোচের সাথে API যাতে মানানসই হয় তা নিশ্চিত করতে আমরা API-তে অনেক পরিবর্তন করেছি।

  • আর এর বেশিরভাগই শুরু হয়েছে রেসিপির উপর আমাদের জোর দেওয়ার মাধ্যমে।

  • হ্যাঁ, Navigation 3-এর অন্যতম প্রধান লক্ষ্য ছিল আপনার পথে বাধা না হওয়া।

  • আপনি যখন কোনও বিশেষ উচ্চাকাঙ্ক্ষী ফিচার তৈরি করতে চান, তখন সেটি বাস্তবে সম্ভব করার জন্য আমাদের কাছ থেকে ফিচার রিকোয়েস্টের প্রয়োজন হবে না।

  • তাই, যখন আমরা এই প্রশ্নটি পাই, "ঠিক আছে, আমি এটি কীভাবে করব?"

  • অথবা, "আমি কীভাবে এই সমস্যার সমাধান করব?"

  • শুরু থেকেই কাস্টম API তৈরি করার পরিবর্তে, আমরা একটি রেসিপি তৈরি করি, একটি নির্দেশিকা যা ব্যাখ্যা করে যে আপনি কীভাবে Navigation 3-এ কোনও পরিবর্তন না করেই আপনার অ্যাপে সেই ফিচার যোগ করতে পারেন।

  • এই ধরনের দেখানোর স্টাইল, না বলা, যা আমাদের GitHub-এ Nav3 রেসিপি রিপোজিটরি তৈরি করতে পরিচালিত করেছে।

  • এতে এখন একাধিক ব্যাক স্ট্যাক এবং স্ক্রিনের নিচে অ্যাটাচ করা শিটের মতো সাধারণ ব্যবহারের ক্ষেত্রে ৩৫টিরও বেশি রেসিপি রয়েছে যা আপনাকে আপনার অ্যাপে এটি প্রয়োগ করার জন্য প্রয়োজনীয় সমস্ত কোড দেয়।

  • এর অর্থ হল, ২০২৫ সালের নভেম্বরে Nav3-এর 1.0 রিলিজের জন্য, আমাদের কাছে ইতিমধ্যেই ডিপ লিঙ্ক প্রয়োগ করার একটি রেসিপি ছিল কারণ আমরা জানতাম যে এটি আপনাদের অনেকের জন্য গুরুত্বপূর্ণ।

  • কিন্তু যখন আমরা বুঝতে পারি যে কোনও রেসিপির জন্য একগুচ্ছ হেল্পার মেথড প্রয়োজন যা আপনাকে কপি করে আপনার অ্যাপে পেস্ট করতে হবে, তখন আমরা গ্র্যাজুয়েশন নামক একটি প্রসেস বিবেচনা করি, যেখানে আমরা সেই হেল্পারদের নিয়ে বলি, "ঠিক আছে, তারা যদি নেভিগেশন থ্রি-এর অংশ হয় তবে আসলে কেমন দেখাবে?"

  • এর ফলে, যেসব ডেভেলপার আগে থেকেই সেই কোড কপি-পেস্ট করেছেন, তাদের কোনও সমস্যা হবে না।

  • আপনি সেই কোডটি রাখতে পারেন।

  • আপনার কাছে কোনও কাস্টম সমাধান থাকলে এবং সেটি কাজ করলে, তা খুবই ভালো।

  • আপনার কোনও ফোর্সড মাইগ্রেশন নেই।

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

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

  • এইভাবে, যে কেউ সেই সমস্যার জন্য সাজেস্ট করা পদ্ধতির মতো রেসিপি খুঁজছেন তার কাছে সবচেয়ে আপ-টু-ডেট সমাধান রয়েছে।

  • অবশ্যই, পরে বোঝা যায় যে, গ্র্যাজুয়েশন প্রসেস আমাদের রেসিপিতে তৈরি করা হেল্পারদের দিকে নজর দেওয়ার সুযোগ দেয় এবং আমরা বলতে পারি, "এগুলি কি সত্যিই এর জন্য সেরা API?"

  • এবং এই ক্ষেত্রে, এটি আমাদের লাইব্রেরিতে সরানোর সাথে সাথে সেই জিনিসগুলিকে উন্নত করার সুযোগ দেয়।

  • ডিপ লিঙ্কের ক্ষেত্রেও ঠিক তাই হয়েছে।

ডিপ লিঙ্ক API ডিজাইন ও URI প্যাটার্ন ম্যাচিং

  • ধন্যবাদ, ইয়ান।

  • তাই, ধারণাগতভাবে, রেসিপি এবং আমাদের ফাইনাল API, দুটিই খুব একই রকম, কারণ Nav 3-তে, আপনি ব্যাক স্ট্যাকের মালিক।

  • এর অর্থ হল, আমাদের ডিপ লিঙ্ক API-এর মূল লক্ষ্য হল প্রাথমিক ব্যাক স্ট্যাক কী হওয়া উচিত তা খুঁজে বের করা, বিশেষ করে আপনার অ্যাপে প্রবেশ করার জন্য কোনও ডিপ লিঙ্কে ক্লিক করলে ব্যবহারকারী কোন স্ক্রিন দেখতে পাবেন তা নির্ধারণ করা।

  • কিন্তু ব্যবহারকারী সেই ওয়েব URI-তে ক্লিক করলে আসলে কী হয়?

  • এখন, আপনি Nav3 ব্যবহার করুন বা না করুন, Android ফ্রেমওয়ার্কের জন্য আপনার অ্যাপের মাধ্যমে কাজ করে এমন প্রতিটি ডিপ লিঙ্কের জন্য ইনটেন্ট ফিল্টার ঘোষণা করতে হবে।

  • এখন, যেখানে এটি আকর্ষণীয় হয়ে ওঠে তা হল সেই ডিপ লিঙ্কটি আপনার অ্যাক্টিভিটি শুরু করার জন্য ট্রিগার করার পরে কী ঘটে।

  • এখানে, ইনপুট হল সেই ইনটেন্ট যার মাধ্যমে আপনার অ্যাপ শুরু হয়, অর্থাৎ যে অ্যাক্টিভিটি শুরু হয়।

  • তবে ডিপ লিঙ্কের অনেক ফর্ম থাকতে পারে।

  • যেমন, আপনি পরীক্ষার জন্য এটি ম্যানুয়ালি তৈরি করতে চাইতে পারেন অথবা Kotlin Multiplatform-এর সুবিধা নিতে চাইতে পারেন।

  • তাই, আমরা ডিপ লিঙ্ক ইনপুটকে KMP-মানানসই DeepLinkRequest ক্লাসে এনক্যাপসুলেট করেছি।

  • Android-এ আমাদের একটি DeepLinkRequest কনস্ট্রাক্টর আছে যা Android ইনটেন্ট গ্রহণ করে।

  • এরপর, আমাদের লক্ষ্য হল, কোনও অনুরোধের ভিত্তিতে, আপনার ব্যাক স্ট্যাকে কী NavKey রাখতে হবে তা আমরা কীভাবে জানব?

  • এখানেই আমাদের DeepLinkMatcher ক্লাস কাজে লাগে।

  • এটি একটি অনুরোধ গ্রহণ করে এবং একটি MatchResult আউটপুট করে, যার মধ্যে মিলের সাথে সম্পর্কিত NavKey থাকে।

  • বিশেষত ওয়েব URI-এর জন্য, আমাদের কাছে একটি UriDeepLinkMatcher আছে যা প্রদত্ত অনুরোধকে MatchResult-এ রূপান্তরিত করার জন্য প্রয়োজনীয় সমস্ত লজিককে এনক্যাপসুলেট করে।

    val matcher = UriDeepLinkMatcher(
      ...,
      ...
    )
  • এখন, UriDeepLinkMatcher-এর প্রথম প্যারামিটার হল URI প্যাটার্ন।

    val matcher = UriDeepLinkMatcher(
      uriPattern = DeepLinkUri(...),
      ...
    )
  • ধরুন, আপনি একটি ADS অ্যাপ তৈরি করছেন এবং হোম স্ক্রিনে ডিপ লিঙ্ক যোগ করতে চান।

  • এখানে, প্যাটার্নটি হল একটি সঠিক URI এবং HTTP ও HTTPS, দুটিই অটোমেটিক ম্যাচ করার জন্য আমরা HTTP স্কিমা বাদ দিতে পারি।

    val matcher = UriDeepLinkMatcher(
      uriPattern = DeepLinkUri("ads.android.com/home"),
      ...
    )
  • কিন্তু আমরা যদি কোনও নির্দিষ্ট ADS সেশনে ডিপ লিঙ্ক করতে চাই, তাহলে সেই ডিপ লিঙ্কে এমন তথ্য থাকে যা আমাদের এক্সট্র্যাক্ট করতে হবে, যেমন সেশন আইডি।

    // target NavKey
    @Serializable data class Session(val id: String) : NavKey
    
    val matcher = UriDeepLinkMatcher(
      uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
      …
    )
  • তাই এই ক্ষেত্রে, আমাদের প্যাটার্ন ডিপ লিঙ্ক থেকে তথ্য এক্সট্র্যাক্ট করার জন্য প্লেসহোল্ডার হিসেবে কাজ করতে কার্লি ব্র্যাকেট ব্যবহার করে।

  • এবং দেখা যাচ্ছে যে যেকোনও ধরনের ইনপুটকে ক্লাস ইনস্ট্যান্সে কনভার্ট করার জন্য আগে থেকেই একটি সহজ লাইব্রেরি রয়েছে, যা হল Kotlin Serialization।

  • তাই আমাদের UriDeepLinkMatcher একটি ইনপুট URI ডিসিরিয়ালাইজ করতে এবং একটি NavKey ইনস্ট্যান্স আউটপুট করতে একটি Kotlin ডিকোডার ব্যবহার করে।

    // target NavKey
    @Serializable data class Session(val id: String) : NavKey
    
    val matcher = UriDeepLinkMatcher(
      uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
      serializer = serializer<Session>()
    )
  • এখন, সেই ডিকোডারকে জানতে হবে কোন ক্লাসের সাথে কাজ করতে হবে, তাই আপনাকে ম্যাচারের মধ্যে আপনার NavKey-এর জন্য একটি সিরিয়ালাইজারও পাস করতে হবে।

    // target NavKey
    @Serializable data class Session(val id: String) : NavKey
    
    val matcher = UriDeepLinkMatcher(
      uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
      serializer = serializer<Session>()
    )
  • এবং ডিকোডারকে সঠিকভাবে কী-এর প্রপার্টির সাথে প্লেসহোল্ডার ম্যাচ করতে সাহায্য করার জন্য, প্লেসহোল্ডারের নাম অবশ্যই প্রপার্টির নামের সাথে মিলতে হবে।

  • সুতরাং, এই উদাহরণে, প্রপার্টির নাম হল সেশনের ID প্যারামিটারের সাথে ম্যাচ করা ID।

  • ঠিক আছে, এখন আমাদের কাছে UriDeepLinkMatcher আছে, তাই ইনকামিং ডিপ লিঙ্কের সাথে আমাদের একটি DeepLinkRequest তৈরি করতে হবে।

    // target NavKey
    @Serializable data class Session(val id: String) : NavKey
    
    val matcher = UriDeepLinkMatcher(
      uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
      serializer = serializer<Session>()
    )
    
    val request = DeepLinkRequest(
      uri = DeepLinkUri("http://ads.android.com/session/abcd")
    )
    
    val matchResult: UriMatchResult<Session>? = matcher.match(request)
    
    val backStack = listOf(matchResult?.key ?: Home)
  • তারপর, আমাদের অনুরোধ পাস করে ম্যাচ ফাংশন কল করার মাধ্যমে ম্যাচিং করা হয়।

  • এটি আপনাকে NavKey সহ একটি নালেবেল MatchResult দেবে, যা আমাদের উদাহরণে সেশন কী হবে, অথবা ফলাফল নাল হলে, আমরা হোম স্ক্রিনে ডিফল্ট করতে পারি।

  • এখন, একাধিক ডিপ লিঙ্ককে একাধিক ম্যাচিং হিসেবে লিখতে হবে: প্রতিটি ডিপ লিঙ্কের জন্য একটি করে ম্যাচিং।

  • যেমন, এখানে আমাদের সেশন কী-এর জন্য দুটি URI প্যাটার্ন আছে: একটি পুরনো নিউজলেটার প্যাটার্নের জন্য এবং অন্যটি সাধারণ ওয়েব URI-এর জন্য।

  • এছাড়াও, ADS হোম স্ক্রিনে আমাদের একটি ডিপ লিঙ্ক আছে, তাই আমরা সেটির জন্য একটি থার্ড ম্যাচিং যোগ করি।

  • এবং সমস্ত ম্যাচিং একই তালিকায় যোগ করা হবে যা আমরা পুনরাবৃত্তি করি।

  • তাই এখন আমাদের ম্যাচ আমাদের তালিকার সমস্ত ম্যাচারের উপর একটি লুপ হয়ে যায়, যেখানে আমরা অনুরোধের সাথে মেলে এমন সমস্ত ম্যাচ খুঁজে পেতে প্রতিটিতে ম্যাচ কল করি।

  • এটি করার জন্য, আমরা mapNotNull ব্যবহার করে অমিলগুলি ফিল্টার করতে পারি এবং শুধুমাত্র সঠিক মিলগুলির একটি তালিকা পেতে পারি।

    val matchers = listOf(...)
    
    // gets all non-null MatchResult
    val matchResults = matchers.mapNotNull { matcher ->
      matcher.match(request)
    }
  • এখন বেশিরভাগ ক্ষেত্রে, আপনার তালিকায় সম্ভবত একটিই নন-নাল ম্যাচ থাকবে, তবে এমন কিছু ক্ষেত্র আছে যেখানে একাধিক সঠিক ম্যাচ থাকতে পারে।

  • যেমন, ধরা যাক আমরা একই সেশন URI প্যাটার্ন আবার ব্যবহার করে একটি বিশেষ কীনোট স্ক্রিন চালু করেছি, কিন্তু এখন আইডি প্লেসহোল্ডারের পরিবর্তে, এতে একটি স্ট্যাটিক কীনোট পাথ রয়েছে।

  • তাই এই ক্ষেত্রে, একই অনুরোধ দুটি ম্যাচারের সাথে মিলতে পারে: সেশন কী যেখানে আইডি কীনোটের সমান এবং কীনোট NavKey।

  • তাই MatchResult যেহেতু Comparable ইমপ্লিমেন্ট করে, আমরা max ফাংশন ব্যবহার করে তালিকার সব সঠিক ম্যাচের তুলনা করে সেরা ম্যাচ খুঁজে পেতে পারি অথবা অনুরোধের সাথে ম্যাচ করে এমন কোনও ডিপ লিঙ্ক না থাকলে সেই পরিস্থিতি সামলানোর জন্য maxOrNull ব্যবহার করতে পারি।

    val matchers = listOf(...)
    
    // gets all non-null MatchResult
    val matchResults = matchers.mapNotNull { matcher ->
      matcher.match(request)
    }
    
    // compare matches
    val backStack = listOf(
      matchResults.maxOrNull()?.key ?: Home
    )
  • এখন, একটি লাইটিনিং টকের জন্য ম্যাচারের একটি স্ট্যাটিক তালিকা অর্থপূর্ণ হতে পারে, তবে আপনার স্ক্রিনগুলি যেভাবে একাধিক মডিউল জুড়ে ছড়িয়ে পড়তে পারে, আমরা আশা করি সেই একই মডিউলগুলি আপনার DI সিস্টেমের মাধ্যমে ম্যাচারের একটি গ্লোবাল তালিকায় অবদান রাখবে।

    // Feature A Module
    @ContributesTo(ActivityScope::class)
    @BindingContainer
    object FeatureAModule {
        @IntoSet
        @Provides
        fun provideDeepLinkMatcher() : UriDeepLinkMatcher<NavKey> =
          UriDeepLinkMatcher(
            uriPattern = DeepLinkUri("ads.android.com/featureA"),
            serializer<FeatureBKey>(),
          )
    }
    
    // in MainActivity
    @Inject lateinit var matchers: Set<UriDeepLinkMatcher<NavKey>>
  • এবং এই পদ্ধতি ব্যবহার করে, আমাদের রেসিপি রেপোতে আমাদের Hilt, Metro এবং Koin রেসিপিগুলি ডিপ লিঙ্ক অন্তর্ভুক্ত করার জন্য আপডেট করা হয়েছে।

টাইপ-সেফ ডিসিরিয়ালাইজেশন ও কাস্টম সিরিয়ালাইজার

  • এখন, ক্লারা উল্লেখ করেছেন যে আমাদের UriDeepLinkMatcher ডিপ লিঙ্ক অনুরোধ থেকে ফিল্ড এক্সট্র্যাক্ট করতে এবং এটিকে আমাদের টাইপ-সেফ ক্লাসে পরিণত করতে Kotlin সিরিয়ালাইজেশন ব্যবহার করে, যেমন আমাদের দেখানো সেশন ক্লাস।

  • এখানে যেমন লেখা আছে, এটি একটি খুব সহজ রূপান্তর।

  • URI হল একটি স্ট্রিং।

  • ফিল্ডটি একটি স্ট্রিং।

  • বাহ, এটা তো খুব সহজ ছিল।

  • কিন্তু আমরা জানতাম যে আমাদের শুধু স্ট্রিং প্রপার্টির বাইরে যেতে হবে।

  • এখানেই Kotlin Serialization আমাদের কাজ করার জন্য একটি মজবুত ভিত্তি প্রদান করে।

  • বুলিয়ান, ইন্টিজার, ফ্লোট, এমনকি এনামের মতো সব প্রিমিটিভ টাইপ আউট অফ দ্য বক্স কাজ করে, ডিপ লিঙ্কের ভ্যালু নির্দিষ্ট টাইপ হিসেবে পার্স করা না গেলে অটোমেটিক ম্যাচিং ব্যর্থ হয় এবং নাল রিটার্ন করে।

  • যেমন, আমাদের সেশনের আইডি প্যারামিটার যদি স্ট্রিংয়ের পরিবর্তে আসলে একটি পূর্ণসংখ্যা হয়, তাহলে স্ট্রিংয়ে পাস করা যা পূর্ণসংখ্যায় পার্স করা যায়নি তা ম্যাচ করবে না।

    // pattern = "http://ads.android.com/session/{id}"
    @Serializable data class Session(val id: Int) : NavKey
  • একইভাবে, আপনি যদি ঐচ্ছিক আর্গুমেন্ট রাখতে চান, যেমন আপনার ওয়েব URL-এর সাথে অ্যাটাচ করা অ্যানালিটিক্স ডেটার জন্য, সেগুলি আপনার 'কী'-এর প্রপার্টি হতে পারে যার ডিফল্ট ভ্যালু আছে।

    // pattern = "http://ads.android.com/session/{id}#referrer={referrer}
    @Serializable data class Session(
      val id: Int,
      val referrer: String? = null,
    ) : NavKey
  • আপনি এখানে লক্ষ্য করবেন যে আমরা আপনার ওয়েব URL-এর স্ট্রাকচার ডিকাপল করার জন্য সত্যিই কঠোর চেষ্টা করেছি, এটি অন্য কোনও টিমের মালিকানাধীন হতে পারে, টাইপ-সেফ ক্লাস বা ক্লাস থেকে যা আপনার অ্যাপে ডিপ লিঙ্ক ম্যাচকে উপস্থাপন করে।

  • কারণ আমি জানি যে আপনাদের মধ্যে কেউ কেউ কিছু অত্যন্ত খারাপ ডিপ লিঙ্ক ম্যানেজ করছেন।

    // "id={id}&base64={placeholderImage}"
    @Serializable data class Session(
      val id: String,
      val placeholderImage: Bitmap? = null
    ) : NavKey
    
    // "jsonBlob={user}"
    @Serializable data class Profile(
      val user: User,
    ) : NavKey
  • আপনি জানেন, হয়ত আপনার ডিপ লিঙ্কে base64 এনকোড করা ছবি অ্যাটাচ করা আছে।

  • আপনি জানেন, হয়ত একটি বিশাল JSON ব্লব যা তারা সিদ্ধান্ত নিয়েছে, "ওহ, আসুন এটি আপনার অ্যাপের প্রতিটি URL-এ রাখি।"

  • এখন, আপনাকে দেওয়া ওয়েব URL পরিবর্তন করতে না পারলেও, আপনি যদি এখনও সেখান থেকে কিছু তথ্য এক্সট্র্যাক্ট করতে চান, তাহলে একটি উপায় আছে।

  • তাই, প্রথম টেকনিক হল আমাদের DeepLinkSerializer ক্লাসকে এক্সটেন্ড করা যাতে সেই আর্গুমেন্ট কীভাবে ডিসিরিয়ালাইজ করা হয় বা স্ট্রিং থেকে আপনার টাইপ-সেফ প্রপার্টিতে ট্রান্সফর্ম করা হয় তা সঠিকভাবে কন্ট্রোল করা যায়।

    object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
      ...
    }
  • DeepLinkSerializer হল Kotlin সিরিয়ালাইজেশনের চারপাশে একটি হেল্পার র‍্যাপার যা তাদের সিরিয়ালাইজার ক্লাস প্রয়োগ করাকে সহজ করে তোলে, কারণ এই ক্ষেত্রে, ডিসিরিয়ালাইজার ইনপুট সবসময় আপনার ওয়েব URL থেকে পাওয়া একটি স্ট্রিং হয়।

    public abstract class DeepLinkSerializer<T : Any> : KSerializer<T>
  • এখন DeepLinkSerializer প্রয়োগ করতে, আপনি তিনটি পদ্ধতি লেখেন।

  • প্রথমে, আপনি একটি সিরিয়ালের নাম বেছে নিন।

    object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
      override val serialName: String
        get() = "Bitmap"
    
      ...
    }
  • নেমস্পেস করার প্রয়োজন নেই, তবে এটি যথেষ্ট অনন্য হতে হবে যাতে Kotlin সিরিয়ালাইজেশন বুঝতে পারে যে কখন আপনার সিরিয়ালাইজার ব্যবহার করতে হবে।

  • এর পরে, আপনাকে ডিসিরিয়ালাইজ প্রয়োগ করতে হবে, যা আপনার ওয়েব URL থেকে সেই স্ট্রিং পাস করে এবং আপনার টাইপ-সেফ অবজেক্ট তৈরি করার জন্য দায়ী।

    object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
      override val serialName: String
        get() = "Bitmap"
    
      override fun deserialize(value: String): Bitmap {
        val imageBytes = Base64.decode(value, Base64.DEFAULT)
       // This better be a small image…
        return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
      }
    
       ...
    
    }
  • শেষে, আপনি সিরিয়ালাইজ প্রয়োগ করবেন, যা বিপরীত পদ্ধতি এবং এটি ব্যবহার করা হয় যদি আপনার একই কী ক্লাস rememberNavBackStackটি কেসের মতো ব্যবহার করা হয়।

    object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
    
      ...
    
      override fun serialize(value: Bitmap): String {
        val byteArrayOutputStream = ByteArrayOutputStream()
        // Compress the bitmap into the output stream (e.g., JPEG or PNG)
        value.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
        val byteArray = byteArrayOutputStream.toByteArray()
        // Encode the byte array to a Base64 string
        return Base64.encodeToString(byteArray, Base64.DEFAULT)
      }
    }
  • এখন আপনি আপনার কাস্টম সিরিয়ালাইজারকে সেই প্রপার্টিতে প্রয়োগ করবেন যার সেই ক্লাস আছে যা একটু বিশেষ, এবং ওয়েব UriDeepLinkMatcher বাকিটা দেখে নেবে।

    object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
      ...
    }
    
    @Serializable data class Session(
      val id: String,
      @Serializable(BitmapSerializer::class)
      val placeholderImage: Bitmap? = null
    ) : NavKey

কাস্টম ম্যাচারের মাধ্যমে URL আর্গুমেন্ট প্রিপ্রসেস করা

  • কিন্তু আপনি যদি সেই Base 64 স্ট্রিং বা আপনার ওয়েব URL-এর সাথে অ্যাটাচ করা JSON থেকে ডেটার সামান্য অংশ চান, তাহলে কী করবেন?

  • সেই ক্ষেত্রে, Kotlin Serialization-এ পৌঁছানোর আগেই আপনার কিছু ওয়েব URL প্রি-প্রসেস করা যুক্তিযুক্ত হতে পারে।

  • এইসব কেস ম্যানেজ করতে, আপনি UriDeepLinkMatcher এক্সটেন্ড করতে এবং এর matchArguments পদ্ধতি ওভাররাইড করতে পারবেন।

  • এখন, আমাদের JSON ব্লবের জন্য, আমরা আসলে যা চাই তা হল সেশন আইডি, যেমন আমরা বাকি কিছুর ব্যাপারে চিন্তা করি না।

  • তাই আমরা সাব-ক্লাস তৈরি করে matchArguments ওভাররাইড করি।

    class SessionUriDeepLinkMatcher(
      uriPattern: DeepLinkUri
    ) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {
    
      override fun matchArguments(
        pathArgs: Map<String, List<String>>,
        queryArgs: Map<String, List<String>>,
        fragmentArgs: Map<String, List<String>>
      ): UriMatchResult<Session>? {
        ...
    }
  • ইনপুট হল সেইসব র স্ট্রিং, আপনার URI পাথ থেকে যোগ করা সব প্লেসহোল্ডার।

  • এবং এটি আমাদের super.matchArguments-এ কল করার আগে সেই ভ্যালুগুলি প্রি-প্রসেস এবং এডিট করার অনুমতি দেয়।

    class SessionUriDeepLinkMatcher(
      uriPattern: DeepLinkUri
    ) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {
    
      override fun matchArguments(
        pathArgs: Map<String, List<String>>,
        queryArgs: Map<String, List<String>>,
        fragmentArgs: Map<String, List<String>>
      ): UriMatchResult<Session>? {
        val finalQueryArgs = buildMap {
          putAll(queryArgs)
          val jsonBlob = remove("session")?.firstOrNull()
          if (jsonBlob != null) { put("session", extractSessionIdFromJson(jsonBlob)) }
        }
        return super.matchArguments(pathArgs, finalQueryArgs, fragmentArgs)
    }
  • এখন, আমরা এখানে সরলীকৃত মান পেতে পারি এবং আমাদের Kotlin সিরিয়ালাইজেশন নিয়ে একেবারেই ঝামেলা করতে হবে না।

ডিপ লিঙ্কের জন্য সিন্থেটিক ব্যাক স্ট্যাক তৈরি করা

  • ধন্যবাদ, ইয়ান।

  • আশা করি, আপনাকে প্রায়ই এইসব টেকনিক ব্যবহার করতে হবে না, তবে আপনি কীভাবে ম্যাচিং আচরণ নিয়ন্ত্রণ করতে পারবেন সেই বিষয়ে আমরা অনেক চিন্তাভাবনা করেছি।

  • ঠিক আছে।

  • ডিপ লিঙ্ক ম্যাচারের সাহায্যে কীভাবে NavKey পাওয়া যায় তা আমরা এখন জানি, তবে শুধুমাত্র নেভিগেশন কী সহ ব্যাক স্ট্যাক ব্যবহারকারীকে সেরা অভিজ্ঞতা নাও দিতে পারে।

  • তাই, 'ফিরে যান' বোতামের সঠিক আচরণ নিশ্চিত করতে, আপনি একটি সিন্থেটিক ব্যাক স্ট্যাক চাইবেন।

  • সিন্থেটিক অংশটি এই সত্য থেকে আসে যে ব্যাক স্ট্যাকে এমন কী থাকে যা ব্যবহারকারী কখনই ম্যানুয়ালি নেভিগেট করেননি, তবে এটি সেই পাথ যা তারা ম্যানুয়ালি নেভিগেট করলে নিতেন।

  • যেমন, শুরু হতে চলা কোনও সেশনের বিজ্ঞপ্তিতে ক্লিক করলে, ব্যবহারকারীকে সরাসরি সেই সেশনে নিয়ে যাওয়া হয়, কিন্তু তিনি সিস্টেম ব্যাক ট্রিগার করলে, আপনি হয়ত চাইবেন যে লঞ্চারে ফিরে যাওয়ার আগে তিনি যেন আপনার হোম স্ক্রিনে ফিরে যান।

  • সেই HomeScreen, সিন্থেটিক ব্যাক স্ট্যাকের অংশ হয়ে যায়।

  • এখন ডিপ লিঙ্ক API-এর জন্য, আমরা দুটি প্রতিযোগিতামূলক প্রয়োজনীয়তার মধ্যে ভারসাম্য বজায় রাখতে চেয়েছিলাম।

  • একদিকে, আমরা জানি যে বিভিন্ন ডিপ লিঙ্ক বা বিভিন্ন ম্যাচারের সিন্থেটিক ব্যাক স্ট্যাক তৈরি করার জন্য বিভিন্ন লজিকের প্রয়োজন হবে।

  • উদাহরণস্বরূপ, প্রতিটি ডিপ লিঙ্কের আগে একটি HomeScreen যোগ করা কাজ করতে পারে যতক্ষণ না আপনি একটি সেটিং স্ক্রিনের ভিতরে একটি সাব-স্ক্রিনে ডিপ লিঙ্ক করতে চান।

  • এখন, সেই নির্দিষ্ট ডিপ লিঙ্কের জন্য আপনার সিন্থেটিক ব্যাক স্ট্যাকে একটি অতিরিক্ত লেয়ার প্রয়োজন।

  • স্বাভাবিকভাবেই, এটি প্রতিটি ডিপ লিংক ম্যাচারের সাথে ব্যাক স্ট্যাক বিল্ডারকে যুক্ত করে।

  • তবে, অন্যদিকে আমরা এটিও জানি যে সঠিক স্ক্রিন দেখানোর জন্য প্রতি মিলি সেকেন্ডে আপনার আসল টাকা খরচ হয়।

  • তাই আমরা প্রতিটি ম্যাচ ফলাফলের জন্য সিন্থেটিক ব্যাক স্ট্যাক গণনা করতে চাইনি।

  • আমরা শুধু সেরা ম্যাচটি চেয়েছিলাম।

  • সুতরাং, আমরা যদি প্রতিটি ম্যাচারের জন্য কাস্টম লজিক চাই, কিন্তু প্রতিটি ম্যাচের জন্য সিন্থেটিক ব্যাক স্ট্যাক তৈরি করতে না চাই, তাহলে আমরা কী করব?

  • তাই, কয়েকবার ভুলভাবে শুরু করার পরে, আমরা এমন একটি পদ্ধতি খুঁজে পেয়েছি যা দুটি প্রয়োজনীয়তার মধ্যে ভারসাম্য বজায় রাখে।

  • আপনার কাছে যে ধরনের ম্যাচিং টুলই থাকুক না কেন, আপনি প্রদত্ত MatchResult-এর সাথে ব্যাক স্ট্যাকটি অলসভাবে তৈরি করতে একটি withBackStack কল করে সেই নির্দিষ্ট ম্যাচিং টুলটি বাড়াতে পারেন।

    val homeMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
      listOf(Home, matchResult.key)
    }
    
    val sessionMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
      listOf(Home, SessionList, matchResult.key)
    }
    
    val backStackMatchers: List<BackStackMatcher<NavKey, NavKey>> = listOf(
      homeMatcher, sessionMatcher
    )
    
    val matchResults = backStackMatchers.mapNotNull { matcher.match(request) }
    val bestMatch: BackStackMatchResult<NavKey, NavKey>? = matchResults.maxOrNull()
    
    val backStack = bestMatch?.backStack ?: listOf(Home)
  • তাই, আমাদের একাধিক ম্যাচিং থাকলে, নির্দিষ্ট NavKey-এর জন্য উপযুক্ত ব্যাক স্ট্যাক তৈরি করতে, প্রতিটির নিজস্ব withBackStack কল থাকবে।

  • আপনার কল করা প্রতিটি ম্যাচিং withBackStack ব্যবহার করলে আপনি সবচেয়ে পরিষ্কার কোড পাবেন, যা আপনাকে ব্যাক স্ট্যাক ম্যাচারের একটি তালিকা দেবে।

  • এবং আপনি যখন সেরা ম্যাচ খুঁজে পান, তখন আপনি যে ম্যাচের ফলাফল পান তা হল BackStackMatchResult।

  • এটি হল MatchResult-এর সাব-ক্লাস যা backStack নামে একটি নতুন প্রপার্টি যোগ করে, এটি হল আপনার withBackStack ল্যাম্বডা দ্বারা তৈরি করা সম্পূর্ণ ব্যাক স্ট্যাক।

  • এই লেয়ারিং অ্যাপ্রোচ আমাদের ম্যাচারের ভিতরের লজিককে আপনার withBackStack ল্যাম্বডার ভিতরে আপনি যে কাস্টম লজিক ব্যবহার করতে চান তা থেকে আলাদা করতে দেয়।

লাইটনিং রাউন্ড, সারাংশ ও রিসোর্স

  • এখন, এটি একটি লাইটিনিং টক, তাই দুর্ভাগ্যবশত, আমাদের কাছে সময় প্রায় শেষ, তবে আমরা নেভিগেশন থ্রিতে থাকা অন্যান্য কয়েকটি ফিচার দেখতে পারি যা আপনি আমাদের নেভ থ্রি রেসিপিতে পাবেন।

  • ঠিক আছে।

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

  • এবং এটি কোনও URI পার্সিং বা কাস্টম সিরিয়ালাইজেশন, Kotlin সিরিয়ালাইজেশনের মাধ্যমে যাওয়ার আগে ঘটে।

  • এছাড়াও, আমরা StaticKeyDeepLinkMatcher অফার করি, বিশেষ করে এমন ক্ষেত্রে যেখানে আপনার NavKey পেতে কোনও পার্সিং করার প্রয়োজন নেই, কিন্তু আপনি এখনও ফিল্টার প্রয়োগ করতে চান।

    @Serializable object ImagesKey: NavKey
    
    // custom filter
    val mimeTypeFilter = DeepLinkMatcher.mimeTypeFilter("image/jpg")
    
    val matcher = StaticKeyDeepLinkMatcher(ImagesKey, listOf(mimeTypeFilter))
  • এটি অ্যাকশন- বা MIME-টাইপ-অনলি ডিপ লিঙ্ক ম্যানেজ করার জন্য উপযুক্ত, যেমন ফটো-এডিটিং অ্যাপ যা JPEG ছবি খোলার কাজ ম্যানেজ করতে পারে।

  • অ্যাকশন ও MIME-এর প্রকারের কথা বলতে গেলে, আপনি যেকোনও কাস্টম RequestExtras আপনার DeepLinkRequest-এর সাথে অ্যাটাচ করতে পারবেন, যা আপনার কাস্টম ফিল্টার ও DeepLinkRequest সাবক্লাস তাদের লজিকের অংশ হিসেবে ব্যবহার করতে পারবে।

  • এটি আপনাকে AB টেস্টিং ফ্রেমওয়ার্ক বা এক্সটার্নাল সিগন্যাল মিক্স করতে দেয়।

  • এবং অতিরিক্তের জন্য আমাদের কাছে সম্পূর্ণ টাইপ-নিরাপত্তা API আছে।

    val extras = requestExtras {
        put(SectionExtrasKey, "A")
    }
  • ওহ, এবং এই সমস্ত লজিক Nav3-এর সাধারণ KMP কোডে রয়েছে, তাই আপনার সমস্ত ম্যাচার এবং ব্যাক স্ট্যাক লজিক প্ল্যাটফর্ম জুড়ে শেয়ার করা যেতে পারে।

  • তাই, আপনি যদি আরও সময় নিয়ে দেখতে চান যে Navigation 3 কীভাবে ডিপ লিঙ্ককে সাপোর্ট করে, তাহলে আমাদের Nav3 রেসিপি চেক করে দেখুন।

  • হ্যাঁ, এবং আমরা আমাদের ডিপ লিঙ্ক API-কে আমাদের DI রেসিপিতে এবং আমাদের হিরো অ্যাপ, NowInAndroid-এও ইন্টিগ্রেট করেছি।

  • আমাদের লাইটিনিং টক দেখার জন্য ধন্যবাদ এবং নেভিগেট করার জন্য শুভেচ্ছা।

  • ধন্যবাদ।

কোড

কোড স্নিপেট

"ডিপ লিঙ্ক API ডিজাইন ও URI প্যাটার্ন ম্যাচিং" ভিডিওর 04:41-এ Kotlin স্নিপেট
val matcher = UriDeepLinkMatcher(
  ...,
  ...
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 04:53-এ Kotlin স্নিপেট
val matcher = UriDeepLinkMatcher(
  uriPattern = DeepLinkUri(...),
  ...
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 05:04-এ Kotlin স্নিপেট
val matcher = UriDeepLinkMatcher(
  uriPattern = DeepLinkUri("ads.android.com/home"),
  ...
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 05:12-এ Kotlin স্নিপেট
// target NavKey
@Serializable data class Session(val id: String) : NavKey

val matcher = UriDeepLinkMatcher(
  uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
  …
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 05:39-এ Kotlin স্নিপেট
// target NavKey
@Serializable data class Session(val id: String) : NavKey

val matcher = UriDeepLinkMatcher(
  uriPattern = DeepLinkUri("ads.android.com/session/{id}"),
  serializer = serializer<Session>()
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 05:48-এ Kotlin স্নিপেট
// target NavKey
@Serializable data class Session(val id: String) : NavKey

val matcher = UriDeepLinkMatcher(
  uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
  serializer = serializer<Session>()
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর ০৬:১২-এ Kotlin স্নিপেট
// target NavKey
@Serializable data class Session(val id: String) : NavKey

val matcher = UriDeepLinkMatcher(
  uriPattern = DeepLinkUri("http://ads.android.com/session/{id}"),
  serializer = serializer<Session>()
)

val request = DeepLinkRequest(
  uri = DeepLinkUri("http://ads.android.com/session/abcd")
)

val matchResult: UriMatchResult<Session>? = matcher.match(request)

val backStack = listOf(matchResult?.key ?: Home)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 07:11-এ Kotlin স্নিপেট
val matchers = listOf(...)

// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
  matcher.match(request)
}
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর 07:48-এ Kotlin স্নিপেট
val matchers = listOf(...)

// gets all non-null MatchResult
val matchResults = matchers.mapNotNull { matcher ->
  matcher.match(request)
}

// compare matches
val backStack = listOf(
  matchResults.maxOrNull()?.key ?: Home
)
"ডিপ লিঙ্ক API ডিজাইন এবং URI প্যাটার্ন ম্যাচিং" ভিডিওর ০৮:০৪-এ Kotlin স্নিপেট
// Feature A Module
@ContributesTo(ActivityScope::class)
@BindingContainer
object FeatureAModule {
    @IntoSet
    @Provides
    fun provideDeepLinkMatcher() : UriDeepLinkMatcher<NavKey> =
      UriDeepLinkMatcher(
        uriPattern = DeepLinkUri("ads.android.com/featureA"),
        serializer<FeatureBKey>(),
      )
}

// in MainActivity
@Inject lateinit var matchers: Set<UriDeepLinkMatcher<NavKey>>
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর ০৯:১৫-এ Kotlin স্নিপেট
// pattern = "http://ads.android.com/session/{id}"
@Serializable data class Session(val id: Int) : NavKey
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর ০৯:২৭-এ Kotlin স্নিপেট
// pattern = "http://ads.android.com/session/{id}#referrer={referrer}
@Serializable data class Session(
  val id: Int,
  val referrer: String? = null,
) : NavKey
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর ০৯:৫৩-এ Kotlin স্নিপেট
// "id={id}&base64={placeholderImage}"
@Serializable data class Session(
  val id: String,
  val placeholderImage: Bitmap? = null
) : NavKey

// "jsonBlob={user}"
@Serializable data class Profile(
  val user: User,
) : NavKey
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর 10:19-এ Kotlin স্নিপেট
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
  ...
}
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর 10:32-এ Kotlin স্নিপেট
public abstract class DeepLinkSerializer<T : Any> : KSerializer<T>
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর 10:51-এ Kotlin স্নিপেট
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
  override val serialName: String
    get() = "Bitmap"

  ...
}
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর ১১:০১-এ Kotlin স্নিপেট
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
  override val serialName: String
    get() = "Bitmap"

  override fun deserialize(value: String): Bitmap {
    val imageBytes = Base64.decode(value, Base64.DEFAULT)
   // This better be a small image…
    return BitmapFactory.decodeByteArray(imageBytes, 0, imageBytes.size)
  }

   ...

}
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর 11:13-এ Kotlin স্নিপেট
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {

  ...

  override fun serialize(value: Bitmap): String {
    val byteArrayOutputStream = ByteArrayOutputStream()
    // Compress the bitmap into the output stream (e.g., JPEG or PNG)
    value.compress(Bitmap.CompressFormat.JPEG, 100, byteArrayOutputStream)
    val byteArray = byteArrayOutputStream.toByteArray()
    // Encode the byte array to a Base64 string
    return Base64.encodeToString(byteArray, Base64.DEFAULT)
  }
}
"টাইপ-সেফ ডিসিরিয়ালাইজেশন এবং কাস্টম সিরিয়ালাইজার" ভিডিওর 11:24-এ Kotlin স্নিপেট
object BitmapSerializer : DeepLinkSerializer<Bitmap>() {
  ...
}

@Serializable data class Session(
  val id: String,
  @Serializable(BitmapSerializer::class)
  val placeholderImage: Bitmap? = null
) : NavKey
"কাস্টম ম্যাচারের সাথে URL আর্গুমেন্ট প্রিপ্রসেস করা" ভিডিওর 12:09-এ Kotlin স্নিপেট
class SessionUriDeepLinkMatcher(
  uriPattern: DeepLinkUri
) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {

  override fun matchArguments(
    pathArgs: Map<String, List<String>>,
    queryArgs: Map<String, List<String>>,
    fragmentArgs: Map<String, List<String>>
  ): UriMatchResult<Session>? {
    ...
}
"কাস্টম ম্যাচারের সাহায্যে URL আর্গুমেন্ট প্রিপ্রসেস করা" ভিডিওর 12:21-এ Kotlin স্নিপেট
class SessionUriDeepLinkMatcher(
  uriPattern: DeepLinkUri
) : UriDeepLinkMatcher(uriPattern, serializer=serializer<Session>()) {

  override fun matchArguments(
    pathArgs: Map<String, List<String>>,
    queryArgs: Map<String, List<String>>,
    fragmentArgs: Map<String, List<String>>
  ): UriMatchResult<Session>? {
    val finalQueryArgs = buildMap {
      putAll(queryArgs)
      val jsonBlob = remove("session")?.firstOrNull()
      if (jsonBlob != null) { put("session", extractSessionIdFromJson(jsonBlob)) }
    }
    return super.matchArguments(pathArgs, finalQueryArgs, fragmentArgs)
}
"ডিপ লিঙ্কের জন্য সিন্থেটিক ব্যাক স্ট্যাক তৈরি করা" ভিডিওর ১৪:৩১-এ Kotlin স্নিপেট
val homeMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
  listOf(Home, matchResult.key)
}

val sessionMatcher = UriDeepLinkMatcher(...).withBackStack { matchResult ->
  listOf(Home, SessionList, matchResult.key)
}

val backStackMatchers: List<BackStackMatcher<NavKey, NavKey>> = listOf(
  homeMatcher, sessionMatcher
)

val matchResults = backStackMatchers.mapNotNull { matcher.match(request) }
val bestMatch: BackStackMatchResult<NavKey, NavKey>? = matchResults.maxOrNull()

val backStack = bestMatch?.backStack ?: listOf(Home)
"লাইটনিং রাউন্ড, সারাংশ এবং রিসোর্স" ভিডিওর ১৫:৫২-এ Kotlin স্নিপেট
@Serializable object ImagesKey: NavKey

// custom filter
val mimeTypeFilter = DeepLinkMatcher.mimeTypeFilter("image/jpg")

val matcher = StaticKeyDeepLinkMatcher(ImagesKey, listOf(mimeTypeFilter))
"লাইটনিং রাউন্ড, সারাংশ এবং রিসোর্স" ভিডিওর ১৬:২৪-এ Kotlin স্নিপেট
val extras = requestExtras {
    put(SectionExtrasKey, "A")
}