পাঠ ২৬ · ৫৭-এর মধ্যে · মডিউল ৬
Home / Courses / Mobile App Development / ডিপ লিংকিং

ডিপ লিংকিং ও ইউনিভার্সাল লিংক

Deep linking & universal links
১০ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

এই পাঠে যা শিখবেন

  • ডিপ লিংক ও ইউনিভার্সাল লিংক কী, এবং কীভাবে এগুলো অ্যাপকে একটি নির্দিষ্ট স্ক্রিনে সরাসরি নিয়ে যায়
  • একটি URL-সদৃশ স্ট্রিংকে partition()/split() দিয়ে scheme, path segments ও query string-এ ভাগ করা — কোনো re মডিউল ছাড়াই
  • পাথ সেগমেন্টকে রুট-নির্দিষ্ট প্যারামিটার নামের সাথে পজিশনাল ম্যাচ করে অর্থবহ path_params তৈরি করা
  • পার্স করা স্ক্রিন ও প্যারামিটার L24-এর নেভিগেশন স্ট্যাকে push() করা

১ · ডিপ লিংক কী এবং কেন পার্স করা দরকার

ডিপ লিংকDeep Linkএকটি URL-সদৃশ স্ট্রিং যা অ্যাপকে চালু করার পাশাপাশি অ্যাপের ভেতরের একটি নির্দিষ্ট স্ক্রিনেও সরাসরি নিয়ে যায়, যেমন একটি নির্দিষ্ট প্রোডাক্ট বা প্রোফাইল পেজ একটি ইমেইল, SMS বা বিজ্ঞাপনে থাকা লিংকে ট্যাপ করলে সরাসরি অ্যাপের ভেতরের প্রাসঙ্গিক স্ক্রিনে পৌঁছে দেয় — শুধু অ্যাপের হোম স্ক্রিনে না নিয়ে গিয়ে। ইউনিভার্সাল লিংক (iOS পরিভাষা) বা App Links (Android পরিভাষা) একই ধারণা, কিন্তু সাধারণ https:// URL ব্যবহার করে — অ্যাপ ইনস্টল না থাকলে একই লিংক ব্রাউজারে ওয়েব পেজ হিসেবেও খোলে। দুই ক্ষেত্রেই OS অ্যাপকে সেই URL-টিই দেয়, আর অ্যাপের দায়িত্ব হলো সেই স্ট্রিং পার্স করে বুঝে নেওয়া কোন স্ক্রিন দেখাতে হবে, কোন ডেটা দিয়ে।

"myapp://product/ 42?ref=email" scheme + rest partition("://") path + query split("?", 1) screen + params nav.push(entry)
প্রতিটি ধাপ হাতে-লেখা partition()/split() — কোনো re মডিউল ছাড়াই — শেষে ফলাফল নেভিগেশন স্ট্যাকে push হয়।

২ · হাতে-লেখা পার্সার — parse_deep_link()

নিচের ফাংশনটি পাঁচটি ধাপে কাজ করে: (১) "://" দিয়ে scheme আলাদা করা, (২) "?" দিয়ে path আর query আলাদা করা, (৩) "/" দিয়ে path-কে সেগমেন্টে ভাগ করা, (৪) প্রথম সেগমেন্টকে "রুট নাম" ধরে বাকি সেগমেন্টগুলোকে ROUTES টেবিলে ঘোষিত প্যারামিটার নামের সাথে পজিশনাল ম্যাচ করা, এবং (৫) "&" ও "=" দিয়ে query string পার্স করা। তিনটি ভিন্ন ডিপ লিংক দিয়ে এটি পরীক্ষা করা হয়েছে — একটিতে path ও query দুটোই আছে, একটিতে শুধু path প্যারামিটার, আর একটিতে শুধু query প্যারামিটার।

Python
# হাতে-লেখা ডিপ-লিংক পার্সার -- শুধু partition()/split() ব্যবহার করে, কোনো re মডিউল ছাড়াই

# রুট টেবিল: প্রথম path সেগমেন্ট (রুট নাম) -> বাকি সেগমেন্টগুলোর জন্য প্যারামিটার নাম
ROUTES = {
    "product": ["id"],
    "user":    ["username"],
}

# রুট নাম -> সেই রুটে দেখানো স্ক্রিনের নাম
SCREEN_MAP = {
    "product": "ProductDetail",
    "user":    "UserProfile",
    "search":  "SearchResults",
}


def parse_deep_link(url):
    # ধাপ ১: scheme আলাদা করা -- partition সবসময় ৩টি অংশ ফেরত দেয় (আগে, sep, পরে),
    # তাই "://" না থাকলেও unpacking কখনো ব্যর্থ হয় না
    scheme, _, rest = url.partition("://")

    # ধাপ ২: path আর query আলাদা করা "?" দিয়ে
    if "?" in rest:
        path_part, query_part = rest.split("?", 1)
    else:
        path_part, query_part = rest, ""

    # ধাপ ৩: path-কে সেগমেন্টে ভাগ করা "/" দিয়ে
    segments = [s for s in path_part.split("/") if s != ""]
    route_name = segments[0] if segments else ""
    path_values = segments[1:]

    # ধাপ ৪: রুট-নির্দিষ্ট প্যারামিটার নামের সাথে বাকি সেগমেন্টগুলো পজিশনাল ম্যাচ করা
    param_names = ROUTES.get(route_name, [])
    path_params = {}
    for i, name in enumerate(param_names):
        if i < len(path_values):
            path_params[name] = path_values[i]

    # ধাপ ৫: query string পার্স করা "&" এবং "=" দিয়ে
    query_params = {}
    if query_part:
        for pair in query_part.split("&"):
            if "=" in pair:
                key, value = pair.split("=", 1)
                query_params[key] = value
            elif pair:
                query_params[pair] = ""

    return {
        "scheme": scheme,
        "screen": SCREEN_MAP.get(route_name, "NotFound"),
        "path_params": path_params,
        "query_params": query_params,
    }


LINKS = [
    "myapp://product/42?ref=email",
    "myapp://user/rafiq99",
    "myapp://search?q=mobile+dev&sort=recent",
]

for link in LINKS:
    parsed = parse_deep_link(link)
    print(f"{link}\n  -> {parsed}\n")

    
তিনটি ফলাফল যাচাই করে দেখুন: প্রথম লিংকে path_params = {'id': '42'} এবং query_params = {'ref': 'email'} — দুটোই আছে। দ্বিতীয় লিংকে শুধু path_params = {'username': 'rafiq99'}, query_params খালি। তৃতীয় লিংকে "search" ROUTES-এ নেই বলে path_params খালি থাকে, কিন্তু query_params = {'q': 'mobile+dev', 'sort': 'recent'} ঠিকই পার্স হয়। লক্ষ্য করুন এটি একটি সরলীকৃত পার্সার — বাস্তব ডিপ-লিংক পার্সারে percent-encoding (%20, + ইত্যাদি) ডিকোড করার একটি অতিরিক্ত ধাপও থাকে, যা এখানে ইচ্ছাকৃতভাবে বাদ দেওয়া হয়েছে (শেখার লক্ষ্য হলো স্প্লিটিং লজিক)।

৩ · পার্স করা ফলাফল নেভিগেশন স্ট্যাকে push() করা

পার্সার একটি স্ক্রিন নাম আর দুই ধরনের প্যারামিটার দেয় — এবার সেগুলো একত্র করে L24-এর NavigationStack-এ push করা হবে। প্রতিটি স্ট্যাক এন্ট্রি এখন থেকে শুধু একটি স্ক্রিন-নাম স্ট্রিং নয়, বরং {"screen": ..., "params": {...}} আকৃতির একটি dict — যাতে প্যারামিটারগুলোও স্ক্রিনের সাথে বহন হয়।

Python
# L24-এর NavigationStack, এই পাতায় নতুন করে সংজ্ঞায়িত
class NavigationStack:
    def __init__(self, root_screen):
        self.stack = [root_screen]

    def push(self, screen):
        self.stack.append(screen)

    def current(self):
        return self.stack[-1]


def push_deep_link(nav, link):
    # আগের সেলে সংজ্ঞায়িত parse_deep_link() পুনরায় ব্যবহার করা হচ্ছে
    parsed = parse_deep_link(link)
    params = {**parsed["path_params"], **parsed["query_params"]}
    entry = {"screen": parsed["screen"], "params": params}
    nav.push(entry)
    return parsed, entry


nav = NavigationStack("Home")
print(f"শুরু -> stack: {nav.stack}\n")

for link in LINKS:
    parsed, entry = push_deep_link(nav, link)
    print(f"deep link : {link}")
    print(f"parsed    : {parsed}")
    print(f"pushed    : {entry}")
    print(f"stack now : {nav.stack}\n")

    
শেষে nav.stack-এর দৈর্ঘ্য ৪ — রুট "Home" আর তিনটি ডিপ লিংক থেকে push হওয়া তিনটি dict এন্ট্রি। প্রতিটি এন্ট্রির "params" কী-তে path_params ও query_params একসাথে মার্জ হয়ে আছে ({**path_params, **query_params}) — বাস্তব কোডে সংঘর্ষ (একই নাম দুই জায়গায়) এড়াতে এই মার্জ অর্ডার সচেতনভাবে বেছে নিতে হয়।
মূল কথা · Key takeaway

একটি ডিপ লিংক আসলে শেষমেশ একটি সাধারণ push() কলে রূপান্তরিত হয় — শুধু স্ক্রিন-নাম আর প্যারামিটারগুলো হার্ডকোড না করে একটি URL-সদৃশ স্ট্রিং পার্স করে বের করা হয়। পার্সিং লজিক নিজেই জটিল কিছু নয় — partition()/split()-এর কয়েকটি ধাপ, যতক্ষণ প্রতিটি ধাপ সাবধানে (missing separator, খালি সেগমেন্ট) হ্যান্ডল করা হয়।

ভাবনার প্রশ্ন

প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।

প্র ০১ কেন url.partition("://") ব্যবহার করা হলো, url.split("://") নয়?

partition() সবসময় ঠিক ৩টি মান ফেরত দেয় — আগে, সেপারেটর, পরে — এমনকি সেপারেটরটি স্ট্রিং-এ না থাকলেও (তখন "আগে" পুরো স্ট্রিং হয়ে যায়, বাকি দুটো খালি স্ট্রিং)। তাই scheme, _, rest = url.partition("://") unpacking কখনো ব্যর্থ হয় না। split("://") ব্যবহার করলে ফেরত আসা তালিকার দৈর্ঘ্য ইনপুটের উপর নির্ভর করে বদলে যেতে পারে (০, ১ বা তার বেশি সেপারেটর থাকলে), যা unpack করার আগে অতিরিক্ত চেক দাবি করতো।

প্র ০২ পাথ প্যারামিটারের নাম (যেমন "id") তো URL স্ট্রিং-এ কোথাও লেখা নেই — "42"-কে কীভাবে {'id': '42'}-এ রূপান্তর করা সম্ভব হলো?

ঠিক ধরেছেন — URL স্ট্রিং শুধু কাঁচা মান ("42") বহন করে, নাম নয়। নামটি আসে অ্যাপের নিজস্ব রুট টেবিল (ROUTES dict) থেকে, যা আগে থেকেই ঘোষণা করে রাখে "product" রুটের প্রথম সেগমেন্টের নাম "id"। এটিই বাস্তব ফ্রেমওয়ার্কগুলো (যেমন /product/:id ধরনের রুট টেমপ্লেট) যেভাবে কাজ করে তার একটি সরলীকৃত সংস্করণ — পজিশনাল মানকে নাম দেওয়ার দায়িত্ব সবসময় অ্যাপের কনফিগারেশনের, URL স্ট্রিং-এর নিজের নয়।

প্র ০৩ "search" রুটটি ROUTES-এ নেই, তবুও তৃতীয় লিংকটি ঠিকমতো "SearchResults" স্ক্রিনে পার্স হলো কেন?

কারণ ROUTES আর SCREEN_MAP দুটো সম্পূর্ণ স্বতন্ত্র উদ্দেশ্যে কাজ করে। ROUTES.get(route_name, []) শুধু বলে "এই রুটের পাথ প্যারামিটারের নাম কী" — "search" এর কোনো পাথ প্যারামিটার নেই বলে এটি খালি তালিকা ফেরত দেয় (লুপ চলেই না, path_params খালি থাকে)। কিন্তু SCREEN_MAP-এ "search" ঠিকই আছে, তাই স্ক্রিন-নাম সঠিকভাবে "SearchResults" রেজলভ হয়। একটি রুট পাথ-প্যারামিটার ছাড়াই সম্পূর্ণ বৈধ হতে পারে।

অনুশীলন

  1. চিন্তা করুন: ধরুন ROUTES-এ একটি নতুন এন্ট্রি যোগ করা হলো — "order": ["id", "tab"] — এবং ডিপ লিংক "myapp://order/981/details" পার্স করা হলো। হাতে-হাতে ট্রেস করে বলুন path_params ডিকশনারিতে চূড়ান্তভাবে কী থাকবে।

    path_part "order/981/details"-কে "/" দিয়ে ভাঙলে segments = ["order", "981", "details"] হয় — route_name = "order", path_values = ["981", "details"]। param_names = ["id", "tab"] এর সাথে পজিশনাল ম্যাচ করলে: i=0 -> path_params["id"] = "981", i=1 -> path_params["tab"] = "details"। চূড়ান্ত ফলাফল: {'id': '981', 'tab': 'details'}।

  2. পরীক্ষা করুন: উপরের প্রথম কোড সেলে LINKS তালিকায় একটি নতুন লিংক যোগ করুন — "myapp://unknown/1" — এবং সেলটি আবার Run করুন। পার্স করা ফলাফলে "screen" কী মান দেখায়, এবং কেন?

    "screen"-এর মান হবে "NotFound" — কারণ SCREEN_MAP.get(route_name, "NotFound") কলে route_name ("unknown") SCREEN_MAP ডিকশনারিতে নেই, তাই ডিফল্ট মান "NotFound" ফেরত আসে। এটি দেখায় পার্সার অজানা রুটেও ক্র্যাশ করে না — বরং একটি নির্দিষ্ট, হ্যান্ডল-করার-উপযোগী ফলাফল দেয়, যা বাস্তব অ্যাপে সাধারণত একটি "পেজ পাওয়া যায়নি" স্ক্রিনে নেভিগেট করায়।

আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — সবগুলো মডিউল একসাথে দেখুন।
  • পুনরায় দেখুন: স্ট্যাক-ভিত্তিক নেভিগেশন L24 NavigationStack ক্লাসের মূল ভিত্তি — এই পাঠে ডিপ লিংক থেকে পার্স করা স্ক্রিন ও প্যারামিটার ঠিক এই স্ট্যাকেই push হয়।
  • সব Courses দেখুন ABCL TECH C, C++, Python, Java, JavaScript, DSA, DBMS, Discrete Mathematics, System Design, Cybersecurity, Cloud Computing & DevOps, Computer Networks, Operating Systems, Computer Architecture, Programming Languages & Compiler Design, Software Engineering & Git, Theory of Computation, Engineering Economics, Full-Stack Web Frameworks ও Mobile App Development — সব এক জায়গায়।
আগের পাঠ
ট্যাব ও ড্রয়ার নেভিগেশন