স্ট্যাক-ভিত্তিক নেভিগেশন
এই পাঠে যা শিখবেন
- নেভিগেশন স্ট্যাক কী এবং কেন এটি "স্ট্যাক" (LIFO) নীতিতে কাজ করে
- একটি সত্যিকারের
NavigationStackক্লাস —push(),pop(),replace(),current() - একটি বাস্তবসম্মত নেভিগেশন সিকোয়েন্স ধাপে ধাপে ট্রেস করা এবং প্রতিটি ধাপের পর স্ট্যাকের অবস্থা যাচাই করা
push()ওreplace()-এর মধ্যে পার্থক্য — এবং বাস্তব অ্যাপে কেন এই পার্থক্যটি গুরুত্বপূর্ণ (যেমন লগইন ফ্লো)
১ · নেভিগেশন স্ট্যাক কী এবং কেন "স্ট্যাক"
নেভিগেশন স্ট্যাকNavigation Stackস্ক্রিনগুলোর একটি অর্ডারড কালেকশন যা LIFO (Last In, First Out) নীতিতে কাজ করে — সবশেষে যোগ হওয়া স্ক্রিনটিই সবার আগে সরে যায়
হলো মোবাইল অ্যাপের সবচেয়ে সাধারণ নেভিগেশন মডেল। ব্যবহারকারী যখন একটি নতুন স্ক্রিনে যান (যেমন একটি লিস্ট থেকে
একটি প্রোডাক্টে ট্যাপ করেন), সেই স্ক্রিনটি বর্তমান স্ক্রিনের উপরে যোগ হয় — আগের স্ক্রিনটি
হারিয়ে যায় না, শুধু নিচে চাপা পড়ে থাকে। ব্যাক বাটনে (বা সোয়াইপ-ব্যাক জেসচারে) চাপলে সবচেয়ে উপরের স্ক্রিনটি
সরে যায় এবং ঠিক নিচের স্ক্রিনটি আবার দৃশ্যমান হয় — এটিই LIFO আচরণ, এবং একটি Python
list-এর append()/pop() ঠিক এই একই আচরণ প্রদান করে।
২ · একটি সত্যিকারের NavigationStack ক্লাস
নিচের ক্লাসটি একটি সাধারণ Python list-কে ঘিরে চারটি মেথড দেয় — push() (নতুন স্ক্রিন
যোগ), pop() (উপরের স্ক্রিন সরানো, কিন্তু রুট স্ক্রিন কখনো সরানো যাবে না), replace()
(বর্তমান স্ক্রিনটি বদলে দেওয়া, গভীরতা না বাড়িয়ে), এবং current() (এই মুহূর্তে কোন স্ক্রিন
দৃশ্যমান তা জানা)। এরপর একটি বাস্তবসম্মত সিকোয়েন্স — Home থেকে Profile, তারপর Settings, তারপর এক ধাপ পপ করে
আবার Profile-এ, তারপর EditProfile — চালিয়ে প্রতিটি ধাপের পর স্ট্যাকের অবস্থা প্রিন্ট করা হয়েছে।
# একটি সত্যিকারের নেভিগেশন স্ট্যাক -- মোবাইল ফ্রেমওয়ার্কে যাকে NavigationController /
# NavHostFragment / Navigator বলা হয়, তার ভেতরে ঠিক এভাবেই একটি list কাজ করে
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 replace(self, screen):
self.stack[-1] = screen
def current(self):
return self.stack[-1]
def show(label, nav):
print(f"{label:32s} -> stack: {nav.stack}")
nav = NavigationStack("Home")
show("শুরু (রুট স্ক্রিন Home)", nav)
nav.push("Profile")
show("push('Profile')", nav)
nav.push("Settings")
show("push('Settings')", nav)
removed = nav.pop()
show(f"pop() # সরানো হলো {removed!r}", nav)
nav.push("EditProfile")
show("push('EditProfile')", nav)
print(f"\nবর্তমান স্ক্রিন (current()): {nav.current()}")
print(f"স্ট্যাকের গভীরতা: {len(nav.stack)}")
+১ (push Profile) +১ (push
Settings) −১ (pop) +১ (push EditProfile) = +২, যা শুরুর গভীরতা ১-এর সাথে যোগ হয়ে ৩ হয়েছে। প্রতিটি
push()/pop() সত্যিই তালিকাটি মিউটেট করছে — এটি কোনো hardcoded স্ট্রিং না, প্রতিবার
show() আসল nav.stack-এর বর্তমান অবস্থাই প্রিন্ট করছে।
৩ · replace() — নতুন এন্ট্রি যোগ না করে বর্তমান স্ক্রিন বদলানো
push() সবসময় স্ট্যাকের গভীরতা ১ বাড়ায়, কিন্তু কখনো কখনো আমরা এমন একটি স্ক্রিনে যেতে চাই যেখান
থেকে ব্যাক বাটনে চাপলে আগের স্ক্রিনে ফিরে যাওয়াই উচিত না — যেমন লগইন সফল হওয়ার পর। এই
ক্ষেত্রে replace() ব্যবহার করা হয় — এটি স্ট্যাকের গভীরতা না বাড়িয়ে শুধু সবচেয়ে উপরের এন্ট্রিটি
বদলে দেয়। নিচের কোড সেলে এই আচরণটি এবং রুট স্ক্রিনে pop() প্রত্যাখ্যাত হওয়ার আচরণ — দুটোই
দেখানো হয়েছে (এই সেলটি উপরের সেলে সংজ্ঞায়িত NavigationStack ও show() পুনরায়
ব্যবহার করে, তাই প্রথমে উপরের সেলটি Run করা দরকার)।
# replace() -- বর্তমান স্ক্রিনটি বদলে দেয়, স্ট্যাকের গভীরতা বাড়ায় না।
# বাস্তব ব্যবহার: লগইন সফল হলে Login স্ক্রিনকে Home দিয়ে replace করা হয়,
# যাতে ব্যাক বাটনে চাপলে ব্যবহারকারী আবার Login স্ক্রিনে ফিরে যেতে না পারে।
# (এই সেলটি আগের সেলের NavigationStack ক্লাস ও show() ফাংশন পুনরায় ব্যবহার করছে)
login_flow = NavigationStack("Login")
show("শুরু (রুট স্ক্রিন Login)", login_flow)
login_flow.push("OTPVerification")
show("push('OTPVerification')", login_flow)
login_flow.replace("Home")
show("replace('Home') # লগইন সফল", login_flow)
print(f"\nগভীরতা এখনও: {len(login_flow.stack)} (replace নতুন এন্ট্রি যোগ করেনি)")
r1 = login_flow.pop()
show(f"pop() # সরানো হলো {r1!r}", login_flow)
r2 = login_flow.pop()
print(f"pop() # রুট স্ক্রিনে পৌঁছানোর পর ফেরত দেয়: {r2!r} -> stack: {login_flow.stack}")
pop() কলটি None ফেরত দেয় এবং স্ট্যাক অপরিবর্তিত থাকে (['Login'])
— কারণ len(self.stack) <= 1 চেক অনুযায়ী রুট স্ক্রিন কখনো সরানো হয় না। বাস্তব অ্যাপেও রুট
স্ক্রিনে ব্যাক বাটনে চাপলে সাধারণত অ্যাপ থেকেই বেরিয়ে যাওয়া হয় (OS/ফ্রেমওয়ার্ক এটি আলাদাভাবে সামলায়) —
নেভিগেশন স্ট্যাক নিজে কখনো খালি হয়ে যায় না।
একটি নেভিগেশন স্ট্যাক আসলে একটি সাধারণ LIFO list — push() নতুন স্ক্রিন যোগ করে
(গভীরতা +১), pop() উপরের স্ক্রিন সরায় (গভীরতা −১, রুটে থামে), আর replace()
গভীরতা না বাড়িয়ে বর্তমান স্ক্রিন বদলে দেয়। এই একই NavigationStack ধারণা পরের তিনটি
পাঠে (ট্যাব নেভিগেশন, ডিপ লিংকিং, ব্যাক-স্ট্যাক সিরিয়ালাইজেশন) বারবার ফিরে আসবে।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
push() আর replace() — দুটোই তো "উপরের স্ক্রিন বদলানো"র মতো মনে হয়, আসল
পার্থক্য কী?
push() স্ট্যাকে একটি সম্পূর্ণ নতুন এন্ট্রি যোগ করে — গভীরতা ১ বাড়ে, এবং ব্যাক বাটনে চাপলে
ঠিক আগের স্ক্রিনে ফেরা যায়। replace() কোনো নতুন এন্ট্রি যোগ করে না — সবচেয়ে উপরের এন্ট্রিটাই
বদলে দেয়, গভীরতা অপরিবর্তিত থাকে। ফলে replace()-এর পর ব্যাক বাটনে চাপলে যে স্ক্রিনটি বদলানো
হয়েছিল সেটিতে না গিয়ে তার নিচের স্ক্রিনে সরাসরি চলে যাওয়া হয়।
প্র ০২
রুট স্ক্রিন কখনো pop() করা যায় না কেন — এটা কি একটি অপ্রয়োজনীয় সীমাবদ্ধতা?
না, এটি ইচ্ছাকৃত। যদি রুট স্ক্রিনও পপ করা যেত, তাহলে স্ট্যাক সম্পূর্ণ খালি হয়ে যেতে পারতো — আর
current() তখন কোনো স্ক্রিন ফেরত দিতে পারতো না (স্ক্রিনে কিছুই দেখানোর থাকতো না)। বাস্তব
মোবাইল ফ্রেমওয়ার্কে রুট স্ক্রিনে ব্যাক বাটনে চাপলে নেভিগেশন স্ট্যাক অপরিবর্তিত রেখেই আলাদাভাবে
"অ্যাপ থেকে বের হওয়া" (বা হোম স্ক্রিনে যাওয়া) হ্যান্ডল করা হয়।
প্র ০৩ প্রথম কোড সেলের সিকোয়েন্স শেষে স্ট্যাকের গভীরতা ৩ কেন — ৪টা অপারেশন (push, push, pop, push) হলেও তো মনে হতে পারে গভীরতা বেশি হবে?
কারণ প্রতিটি অপারেশনের নিট প্রভাব যোগ করতে হবে, গণনা নয়: push গভীরতা +১ করে, pop
গভীরতা −১ করে। তাই push, push, pop, push = +১ +১ −১ +১ = +২। শুরুর গভীরতা ১
(শুধু Home) + এই +২ = চূড়ান্ত গভীরতা ৩ (['Home', 'Profile', 'EditProfile'])।
অনুশীলন
-
চিন্তা করুন: একটি ই-কমার্স অ্যাপে ফ্লো এরকম — Home → ProductList → ProductDetail →
Checkout → OrderConfirmation।
OrderConfirmationস্ক্রিন দেখানোর সময়push()নাকিreplace()ব্যবহার করা উচিত, যাতে অর্ডার সম্পন্ন হওয়ার পর ব্যাক বাটনে চাপলে ব্যবহারকারী আবারCheckout-এ (এবং সম্ভাব্য দ্বিতীয়বার পেমেন্টের ঝুঁকিতে) ফিরে যেতে না পারেন?replace()ব্যবহার করা উচিত। এটিCheckoutএন্ট্রিটিকে সরাসরিOrderConfirmationদিয়ে বদলে দেবে — স্ট্যাকেCheckoutআর অবশিষ্ট থাকবে না, তাই ব্যাক বাটনে চাপলে সরাসরিProductDetail-এ (Checkout-এর নিচের স্ক্রিনে) চলে যাওয়া হবে, দ্বিতীয়বার পেমেন্ট ফর্ম দেখার কোনো সুযোগ থাকবে না। -
পরীক্ষা করুন: উপরের প্রথম কোড সেলের একদম শেষে (
print(f"স্ট্যাকের গভীরতা...লাইনের পরে) নতুন লাইন যোগ করুন —nav.pop()-কে একটি ভ্যারিয়েবলে রেখে সেটি ও নতুনnav.stackপ্রিন্ট করুন —EditProfileসরানোর পর স্ট্যাক কী হয় দেখুন এবং এটি যৌক্তিক কিনা যাচাই করুন।এই
pop()কলটি'EditProfile'সরিয়ে দেবে এবং স্ট্যাক হয়ে যাবে['Home', 'Profile']— অর্থাৎ ঠিক সেই অবস্থায় ফিরে যাবে যাEditProfileপুশ করার আগে ছিল। এটিই প্রমাণ করেpop()সত্যিই সবচেয়ে সাম্প্রতিকpush()-কে পূর্বাবস্থায় ফেরাচ্ছে — LIFO আচরণ পুরোপুরি বিপরীতমুখী (reversible)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — সবগুলো মডিউল একসাথে দেখুন।
- Full-Stack Web Frameworks — SPA রাউটিং সহোদর কোর্স ব্রাউজারের URL ও history API দিয়ে ক্লায়েন্ট-সাইড রাউটিং কীভাবে কাজ করে — একই মূল ধারণা (একটি নেভিগেশন হিস্ট্রি), ভিন্ন মেকানিজম।
- সব 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 — সব এক জায়গায়।