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

নেভিগেশন স্টেট ও ব্যাক-স্ট্যাক ম্যানেজমেন্ট

Navigation state & back-stack management
৮ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কেন একটি ইন-মেমোরি নেভিগেশন স্ট্যাক M1-এর killed ট্রানজিশনে স্বয়ংক্রিয়ভাবে বেঁচে থাকে না
  • serialize_stack() — একটি লাইভ NavigationStack-কে একটি স্বাধীন, সরল তালিকায় রূপান্তর
  • restore_stack() — সেই সংরক্ষিত তালিকা থেকে একটি সম্পূর্ণ নতুন, কার্যকর NavigationStack পুনর্গঠন
  • equality check ও আচরণগত (behavioral) পরীক্ষা দিয়ে নিশ্চিত করা রিস্টোর করা স্ট্যাক আসল স্ট্যাকের মতোই নির্ভরযোগ্য

১ · সমস্যা: নেভিগেশন স্ট্যাক মেমোরিতেই থাকে, কিন্তু killed মেমোরি মুছে দেয়

L24-এর NavigationStack ক্লাসটি ভেতরে একটি সাধারণ Python list — এবং যেকোনো ইন-মেমোরি ডেটার মতো, অ্যাপ প্রসেস killed স্টেটে গেলে (M1-এ শেখা লাইফসাইকেল ট্রানজিশন) এই list-ও পুরোপুরি মুছে যায়। ব্যবহারকারী যখন অ্যাপটি আবার খোলেন, OS একটি সম্পূর্ণ নতুন প্রসেস চালু করে — কোনো পুরনো নেভিগেশন হিস্ট্রি স্বয়ংক্রিয়ভাবে ফিরে আসে না, যদি না অ্যাপ নিজে থেকে সেটি killed-এ যাওয়ার আগে কোথাও (ডিস্কে) সংরক্ষণ করে রাখে এবং রিলঞ্চের সময় সেখান থেকে পড়ে নেয়। এই পাঠে সেই সংরক্ষণ ও পুনরুদ্ধার প্রক্রিয়াটি সিমুলেট করা হবে — ডিস্কে প্রকৃত লেখা এই স্যান্ডবক্সে সম্ভব নয় (M8-এ (L32) বাস্তব key-value স্টোরেজ এপিআই কভার হবে), তাই এখানে একটি সাধারণ Python ভ্যারিয়েবল দিয়ে "সংরক্ষিত ডেটা"-কে প্রতিনিধিত্ব করা হয়েছে।

লাইভ NavigationStack foreground serialize_stack() সংরক্ষিত list background -> killed রিলঞ্চ: restore_stack()
সংরক্ষিত তালিকাটিই (serialize_stack-এর ফলাফল) একমাত্র জিনিস যা killed ট্রানজিশনের পরও বেঁচে থাকে — restore_stack() এটি দিয়ে একটি নতুন, সমানভাবে কার্যকর স্ট্যাক পুনর্গঠন করে।

২ · serialize_stack() — লাইভ স্ট্যাক থেকে একটি স্বাধীন স্ন্যাপশট

সিরিয়ালাইজেশন এখানে খুব সরল — শুধু স্ক্রিন-নামের একটি নতুন list তৈরি করা, যা মূল nav.stack থেকে স্বাধীন (একই অবজেক্টের রেফারেন্স নয়)। নিচের সেলে একটি নেভিগেশন সেশন তৈরি করে সিরিয়ালাইজ করা হয়েছে।

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

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

    def pop(self):
        if len(self.stack) <= 1:
            return None
        return self.stack.pop()

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


def serialize_stack(nav):
    # একটি সরল, JSON-উপযোগী প্রতিনিধিত্ব -- মূল list-এর একটি স্বাধীন কপি
    return list(nav.stack)


def restore_stack(saved_screens):
    # সংরক্ষিত তালিকা থেকে একটি সম্পূর্ণ নতুন, কার্যকর NavigationStack পুনর্গঠন
    restored = NavigationStack(saved_screens[0])
    for screen in saved_screens[1:]:
        restored.push(screen)
    return restored


# একটি বাস্তবসম্মত নেভিগেশন সেশন তৈরি করা হলো
nav = NavigationStack("Home")
nav.push("Profile")
nav.push("Settings")
nav.push("EditProfile")
print(f"অ্যাপ কিল হওয়ার আগে stack: {nav.stack}")

saved = serialize_stack(nav)
print(f"সংরক্ষিত (serialize_stack ফলাফল): {saved}")
print(f"এটি একই list অবজেক্ট কিনা: {saved is nav.stack}")

    
saved is nav.stack প্রিন্ট করে False — কারণ list(nav.stack) একটি সম্পূর্ণ নতুন list তৈরি করে, শুধু একই কনটেন্ট কপি করে। এটি ইচ্ছাকৃত: nav এখনও লাইভ থাকা অবস্থায় আরও push()/pop() হলে সেগুলো saved-কে প্রভাবিত করবে না — saved ঠিক সেই মুহূর্তের একটি ফ্রোজেন স্ন্যাপশট।

৩ · সিমুলেটেড অ্যাপ-কিল + রিলঞ্চ — রিস্টোর করা স্ট্যাক যাচাই

এবার M1-এর background → killed ট্রানজিশনটি সিমুলেট করা হবে — মূল nav অবজেক্টটি মুছে দেওয়া হবে (del nav), শুধু saved তালিকাটি "বেঁচে" থাকবে। তারপর restore_stack() দিয়ে একটি নতুন স্ট্যাক পুনর্গঠন করে দুটি জিনিস যাচাই করা হবে — প্রথমত এর বিষয়বস্তু আগের মতোই কিনা, দ্বিতীয়ত এটি থেকে pop() করলে সেই একই আচরণ হয় কিনা যা কিল হওয়ার আগে হতো।

Python
# --- M1 লাইফসাইকেল: background -> killed সিমুলেট করা হলো ---
# আসল nav অবজেক্টটি এখন মুছে গেলো -- শুধু 'saved' (ডিস্কে সংরক্ষিত ধরে নেওয়া তালিকা) বেঁচে আছে
del nav

# --- রিলঞ্চ: সংরক্ষিত তালিকা থেকে স্ট্যাক পুনর্গঠন করা হলো ---
restored_nav = restore_stack(saved)
print(f"রিস্টোর করা stack: {restored_nav.stack}")

# যাচাই ১: রিস্টোর করা স্ট্যাকের বিষয়বস্তু ও অর্ডার হুবহু সংরক্ষিত তালিকার সাথে মেলে কিনা
identical = restored_nav.stack == saved
print(f"যাচাই ১ -- restored_nav.stack == saved: {identical}")
assert identical

# যাচাই ২: রিস্টোর করা স্ট্যাক থেকে pop() করলে ঠিক সেই স্ক্রিনই ফেরত আসে যা কিল হওয়ার আগে আসতো
expected_next_pop = saved[-1]              # কিল হওয়ার মুহূর্তে সবচেয়ে উপরের স্ক্রিন
actual_pop = restored_nav.pop()
print(f"\nকিল হওয়ার আগে যে স্ক্রিন pop() হতো: {expected_next_pop!r}")
print(f"রিস্টোরের পর pop() যা ফেরত দিলো:      {actual_pop!r}")
assert actual_pop == expected_next_pop

print(f"\npop()-এর পরে রিস্টোর করা stack: {restored_nav.stack}")

    
দুটো assertই পাস করে — প্রমাণ করে রিস্টোর করা স্ট্যাক শুধু "দেখতে একই রকম" নয়, বরং আচরণগতভাবেও ঠিক সেই লাইভ স্ট্যাকের মতোই কাজ করে যেটি কিল হওয়ার আগে ছিল। বাস্তব ডিভাইসে saved তালিকাটি ডিস্কে (ফাইল বা key-value স্টোরেজে) লেখা থাকতো — এই স্যান্ডবক্স শুধু ইন-মেমোরি ভ্যারিয়েবল দিয়ে সেই ধারণাটি সিমুলেট করছে; M8-এর (L32) key-value স্টোরেজ পাঠে প্রকৃত get()/ set() এপিআই কভার হবে, আর M7-এর (L31) স্টেট-রিস্টোরেশন পাঠে ঠিক এই একই প্যাটার্ন ফর্ম-ডেটার মতো অন্য ধরনের স্টেটেও প্রয়োগ হবে।
মূল কথা · Key takeaway

একটি নেভিগেশন স্ট্যাক ইন-মেমোরি ডেটা — killed ট্রানজিশনে এটি স্বয়ংক্রিয়ভাবে বেঁচে থাকে না। serialize_stack() এটিকে একটি স্বাধীন, সংরক্ষণযোগ্য তালিকায় রূপান্তর করে; restore_stack() সেই তালিকা থেকে একটি নতুন, সমানভাবে কার্যকর স্ট্যাক পুনর্গঠন করে — push()/pop() এর মাধ্যমেই, কোনো শর্টকাট ছাড়াই। equality check ও আচরণগত পরীক্ষা দুটোই একসাথে নিশ্চিত করে রিস্টোরেশন সত্যিই নির্ভরযোগ্য।

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

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

প্র ০১ serialize_stack() কেন list(nav.stack) (একটি নতুন কপি) রিটার্ন করে, শুধু nav.stack রিটার্ন করে না?

শুধু nav.stack রিটার্ন করলে saved আসল list অবজেক্টেরই আরেকটি নাম হতো (একই মেমোরি রেফারেন্স) — তখন nav এখনও লাইভ থাকা অবস্থায় কেউ যদি আরও push()/pop() করে, সেই পরিবর্তন saved-এও দেখা যেত, যদিও saved-এর উদ্দেশ্য ছিল একটি নির্দিষ্ট মুহূর্তের স্ন্যাপশট ধরে রাখা। list(nav.stack) একটি সম্পূর্ণ স্বাধীন কপি তৈরি করে, তাই স্ন্যাপশটটি সত্যিই "ফ্রোজেন" থাকে।

প্র ০২ restore_stack() সরাসরি restored.stack = saved_screens না করে NavigationStack(saved_screens[0]) দিয়ে শুরু করে বাকিগুলো এক এক করে push() করে কেন?

দুটো কারণ। প্রথমত, সরাসরি restored.stack = saved_screens করলে আবারও একই অ্যালিয়াসিং সমস্যা হতো — restored.stack আর saved একই list অবজেক্ট হয়ে যেত। দ্বিতীয়ত, প্রতিটি স্ক্রিন push() মেথডের মধ্য দিয়ে যোগ করা মানে রিস্টোরেশন ঠিক সেই একই, ভ্যালিডেটেড পথ ব্যবহার করে যা স্বাভাবিক নেভিগেশনও ব্যবহার করে — ভবিষ্যতে push()-এ কোনো অতিরিক্ত লজিক (যেমন সর্বোচ্চ গভীরতা চেক) যোগ হলে, রিস্টোরেশনও স্বয়ংক্রিয়ভাবে সেই একই নিয়ম মেনে চলবে।

প্র ০৩ দ্বিতীয় কোড সেলে equality check restored_nav.stack == saved-এর সাথে তুলনা করা হয়েছে, মূল nav.stack-এর সাথে নয় কেন?

কারণ del nav-এর পর মূল nav অবজেক্টটি আর অস্তিত্বেই নেই — ঠিক যেমন বাস্তবে একটি killed অ্যাপ প্রসেসের মেমোরি সম্পূর্ণ মুছে যায়। saved তালিকাটিই একমাত্র জিনিস যা "বেঁচে" ছিল, তাই এটিই একমাত্র বাস্তবসম্মত তুলনার ভিত্তি — ঠিক যেমন বাস্তব অ্যাপকেও প্রমাণ করতে হয় তার রিস্টোরেশন লজিক ডিস্কে যা লেখা ছিল তার সাথে মেলে, কোনো "আগের মেমোরি" এর সাথে নয় (সেটি আর নেই)।

অনুশীলন

  1. চিন্তা করুন: প্রথম কোড সেলে saved = serialize_stack(nav) লাইনের ঠিক পরে (কিন্তু del nav-এর আগে) যদি আরেকটি nav.push("Notifications") কল করা হতো, তাহলে সেই পরিবর্তন কি saved তালিকাতেও দেখা যেত? কেন বা কেন নয়?

    না, দেখা যেত না। saved ইতিমধ্যেই list(nav.stack) দিয়ে একটি স্বাধীন কপি হিসেবে তৈরি হয়ে গিয়েছিল সেই push() কল হওয়ার আগেই — তাই nav.stack-এ পরে যা যোগ হোক না কেন, saved তার সেই পুরনো, ফ্রোজেন অবস্থাতেই থাকবে (['Home', 'Profile', 'Settings', 'EditProfile'])। এই "নতুন" Notifications স্ক্রিনটি সংরক্ষিত ধরতে হলে serialize_stack(nav) আবার কল করে saved-কে নতুন করে বসাতে হতো।

  2. পরীক্ষা করুন: দ্বিতীয় কোড সেলের একদম শেষে দুটো নতুন লাইন যোগ করুন — restored_nav.pop() আরও দুইবার কল করুন (মোট স্ট্যাক রুট "Home"-এ পৌঁছে যাবে), তারপর তৃতীয়বার restored_nav.pop() কল করে ফলাফল প্রিন্ট করুন — এটি কি রুট স্ক্রিন সরিয়ে দেয়, নাকি L24-এর মতোই প্রত্যাখ্যান করে?

    দুইবার আরও pop() করলে স্ট্যাক ['Home']-এ নেমে আসবে (Settings আর Profile সরে যাবে)। তৃতীয়বার pop() কল করলে এটি রুট স্ক্রিনে পৌঁছে গেছে বলে len(self.stack) <= 1 চেক অনুযায়ী None ফেরত দেবে এবং স্ট্যাক অপরিবর্তিত থাকবে — কারণ restore_stack() একটি সম্পূর্ণ স্বাভাবিক NavigationStack ইনস্ট্যান্স তৈরি করে, তাই এর pop() মেথড ঠিক L24-এর মূল ক্লাসের মতোই আচরণ করে।

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

আগের পাঠ
ডিপ লিংকিং ও ইউনিভার্সাল লিংক