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

কম্পোনেন্ট-লোকাল স্টেট বনাম অ্যাপ-ওয়াইড স্টেট

Component-local state vs app-wide state
৭ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • লোকাল স্টেট বনাম অ্যাপ-ওয়াইড স্টেটের সুনির্দিষ্ট সংজ্ঞা ও কখন কোনটি প্রয়োজন
  • নেভিগেশন প্যারামের মাধ্যমে ডেটা পাস করলে কেন একাধিক স্ক্রিন জুড়ে সিঙ্ক রাখা কষ্টকর হয়ে ওঠে
  • একটি সত্যিকারের Store ক্লাস তৈরি — অপরিবর্তনীয় (immutable) state, set_state(partial) ও subscribe(listener)
  • দুটি স্বতন্ত্র স্ক্রিন কীভাবে একই স্টোরে সাবস্ক্রাইব করে সরাসরি সিঙ্কে থাকে — কোনো প্যারাম পাসিং ছাড়াই

১ · লোকাল স্টেট: একটি স্ক্রিনের নিজস্ব সম্পত্তি

কম্পোনেন্ট-লোকাল স্টেটComponent-Local Stateএমন ডেটা যা একটি নির্দিষ্ট স্ক্রিন/কম্পোনেন্টের ভেতরে তৈরি হয়, শুধু সেখানেই ব্যবহৃত হয়, এবং সেই স্ক্রিন ধ্বংস হলে হারিয়ে যায়। সবচেয়ে সাধারণ উদাহরণ — একটি স্ক্রিনের ভেতরের একটি কাউন্টার, একটি ফর্মের "এখনো সেভ হয়নি" এমন খসড়া টেক্সট, বা একটি অ্যাকর্ডিয়ন খোলা আছে কি না — এসব শুধু সেই একটি স্ক্রিনের নিজস্ব বিষয়, অন্য কোনো স্ক্রিনের জানার দরকার নেই।

Python
# লোকাল স্টেট -- প্রতিটি স্ক্রিন-ইনস্ট্যান্সের নিজস্ব, সম্পূর্ণ স্বতন্ত্র

class CounterScreen:
    def __init__(self, name):
        self.name = name
        self.count = 0  # শুধু এই স্ক্রিন-ইনস্ট্যান্সেরই সম্পত্তি

    def tap_increment(self):
        self.count += 1
        print(f"[{self.name}] ট্যাপ করা হলো -- count = {self.count}")


home_counter = CounterScreen("হোম স্ক্রিনের কাউন্টার")
home_counter.tap_increment()
home_counter.tap_increment()
home_counter.tap_increment()

# ব্যবহারকারী এবার সম্পূর্ণ ভিন্ন একটি স্ক্রিনে গেলো -- এটির নিজস্ব, আলাদা CounterScreen ইনস্ট্যান্স
settings_counter = CounterScreen("সেটিংস স্ক্রিনের কাউন্টার")
settings_counter.tap_increment()

print(f"\nহোম স্ক্রিনের count: {home_counter.count}")
print(f"সেটিংস স্ক্রিনের count: {settings_counter.count}")
print("লক্ষ্য করুন -- দুটি সম্পূর্ণ স্বতন্ত্র সংখ্যা, একটি অন্যটিকে প্রভাবিত করেনি।")

    
এই CounterScreen-এর প্রতিটি ইনস্ট্যান্স নিজের count নিয়ে সম্পূর্ণ স্বাধীন — ঠিক যেভাবে সত্যিকারের মোবাইল অ্যাপে একটি স্ক্রিনের ভেতরের একটি সাধারণ কাউন্টার কাজ করার কথা। সমস্যাটি তখনই শুরু হয় যখন একই সংখ্যাটি দুটি ভিন্ন স্ক্রিনে "একই" থাকতে হবে বলে চাওয়া হয়।

২ · সমস্যা: একই ডেটা দুটি স্বতন্ত্র স্ক্রিনে দরকার হলে

ধরা যাক একটি শপিং অ্যাপে "কার্টে থাকা আইটেম সংখ্যা" দুটি জায়গায় দেখাতে হবে — হোম ট্যাব-এর উপরের একটি ব্যাজে, এবং সম্পূর্ণ স্বতন্ত্র কার্ট ট্যাব-এ। এই দুটি স্ক্রিন M6-এ শেখা নেভিগেশন স্ট্যাকের মাধ্যমে একে অপরের সরাসরি রেফারেন্স রাখে না — একটি থেকে অন্যটিতে সরাসরি settings_counter = home_counter-এর মতো লিখে দেওয়া সম্ভব নয়।

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

হোম স্ক্রিন কার্ট স্ক্রিন প্রতিটি নেভিগেশনে ম্যানুয়াল প্যারাম (ভঙ্গুর) কেন্দ্রীয় Store state + subscribe() subscribe subscribe
প্যারাম-পাসিং সরাসরি এক স্ক্রিন থেকে আরেক স্ক্রিনে যায় (ভঙ্গুর, প্রতিটি ট্রানজিশনে নির্ভরশীল); কেন্দ্রীয় স্টোর উভয় স্ক্রিনকে সরাসরি সাবস্ক্রাইব করতে দেয়, কোনো নেভিগেশন-কল ছাড়াই।

৩ · অ্যাপ-ওয়াইড স্টেট: একটি সত্যিকারের Store ক্লাস

নিচে একটি বাস্তব, কার্যকর Store ক্লাস — এর তিনটি অংশ: state (বর্তমান ডেটা), set_state(partial) (নতুন ডেটা মার্জ করে একটি সম্পূর্ণ নতুন ডিকশনারি তৈরি করে — পুরনোটি সরাসরি মিউটেট করে না), এবং subscribe(listener) (যেকোনো স্ক্রিন নিজেকে নোটিফিকেশনের জন্য নিবন্ধন করতে পারে)।

Python
# একটি সত্যিকারের স্টোর/অবজার্ভার প্যাটার্ন -- দুটি স্বতন্ত্র স্ক্রিন সিঙ্কে থাকে, কোনো প্যারাম পাসিং ছাড়াই

class Store:
    def __init__(self, initial_state):
        self.state = dict(initial_state)
        self._subscribers = []

    def set_state(self, partial):
        # পুরনো state মিউটেট না করে সম্পূর্ণ নতুন একটি dict তৈরি -- অপরিবর্তনীয়তা (immutability)
        self.state = {**self.state, **partial}
        for listener in self._subscribers:
            listener(self.state)

    def subscribe(self, listener):
        self._subscribers.append(listener)


cart_store = Store({"cart_count": 0})

# হোম ট্যাব -- একটি ব্যাজ যা কার্ট-সংখ্যা দেখায়
home_badge_log = []
cart_store.subscribe(lambda state: home_badge_log.append(state["cart_count"]))

# কার্ট ট্যাব -- একটি হেডার যা একই সংখ্যা দেখায়, সম্পূর্ণ স্বতন্ত্র স্ক্রিন
cart_header_log = []
cart_store.subscribe(lambda state: cart_header_log.append(state["cart_count"]))

print("ব্যবহারকারী প্রোডাক্ট পেজ থেকে একটি আইটেম কার্টে যোগ করলো...")
cart_store.set_state({"cart_count": cart_store.state["cart_count"] + 1})

print("ব্যবহারকারী আরও একটি আইটেম যোগ করলো...")
cart_store.set_state({"cart_count": cart_store.state["cart_count"] + 1})

print(f"\nহোম ট্যাবের ব্যাজ যা যা মান পেয়েছে: {home_badge_log}")
print(f"কার্ট ট্যাবের হেডার যা যা মান পেয়েছে: {cart_header_log}")
print(f"স্টোরের বর্তমান state: {cart_store.state}")
print("লক্ষ্য করুন -- দুটি সম্পূর্ণ স্বতন্ত্র স্ক্রিন একই আপডেট একসাথে পেয়েছে, কোনো নেভিগেশন প্যারাম ছাড়াই।")

    
set_state প্রতিবার {**self.state, **partial} দিয়ে একটি সম্পূর্ণ নতুন ডিকশনারি তৈরি করে — এটি Full-Stack Web Frameworks কোর্সের Redux-স্টাইল প্যাটার্নের একই মূলনীতি (অপরিবর্তনীয় স্টেট), শুধু মোবাইল স্ক্রিনের প্রেক্ষাপটে ছোট আকারে প্রয়োগ করা।
মূল কথা · Key takeaway

লোকাল স্টেট একটি স্ক্রিনের ভেতরে থাকা তথ্যের জন্য ঠিক আছে — এটিকে অহেতুক গ্লোবাল করার দরকার নেই। কিন্তু যখন একাধিক স্বতন্ত্র স্ক্রিন/ট্যাবের একই ডেটা "একসাথে সত্য" থাকতে হয়, তখন একটি কেন্দ্রীয় Store — যেখানে প্রতিটি স্ক্রিন সরাসরি সাবস্ক্রাইব করে — নেভিগেশন প্যারামের মাধ্যমে ম্যানুয়ালি ডেটা পাস করার চেয়ে অনেক বেশি নির্ভরযোগ্য। L29 এই স্টোর প্যাটার্নকে অ্যাপ লাইফসাইকেলের সাথে যুক্ত করবে।

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

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

প্র ০১ একটি স্ক্রিনের ভেতরের প্রতিটি ছোট ডেটাকে (যেমন একটি বাটনের "লোডিং" স্টেট) কি সবসময় গ্লোবাল স্টোরে রাখা উচিত?

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

প্র ০২ নেভিগেশন প্যারাম দিয়ে ডেটা পাস করা কেন দুটি "একসাথে লাইভ" থাকা স্ক্রিনের (যেমন দুটি ট্যাব) জন্য কাজ করে না?

নেভিগেশন প্যারাম শুধু একটি স্ক্রিন থেকে আরেকটি স্ক্রিনে যাওয়ার সময় একবারের জন্য ডেটা পাস করে — এরপর দুটি স্ক্রিন একে অপরের থেকে সম্পূর্ণ বিচ্ছিন্ন। যদি ট্যাব A-তে থাকা অবস্থায় ট্যাব B-এর ডেটা বদলে যায় (কোনো নেভিগেশন-কল ছাড়াই), তাহলে ট্যাব A-এর কাছে সেই পরিবর্তনের কোনো খবর পৌঁছানোর প্রক্রিয়াই থাকে না — যদি না উভয়ে একটি শেয়ার্ড স্টোরে সাবস্ক্রাইব করা থাকে।

প্র ০৩ উপরের কোড সেলে set_state যদি নতুন dict তৈরি না করে সরাসরি self.state["cart_count"] += 1 করত, তাহলে কী সমস্যা হতে পারত?

তখন পুরনো state অবজেক্টটিই মিউটেট হয়ে যেত — যদি কোনো কোড আগের state-এর একটি রেফারেন্স ধরে রেখেছিল (যেমন "পরিবর্তনের আগে-পরে তুলনা করার" জন্য), সেই পুরনো রেফারেন্সও অজান্তেই বদলে যেত, কারণ এটি একই অবজেক্ট। নতুন dict তৈরি করলে পুরনো state অপরিবর্তিত থাকে — যা তুলনা, ডিবাগিং ও প্রেডিক্টেবল আচরণের জন্য গুরুত্বপূর্ণ।

অনুশীলন

  1. চিন্তা করুন: আপনার ব্যবহৃত কোনো অ্যাপে এমন ডেটার কথা মনে করুন যা একাধিক স্ক্রিনে "একসাথে সত্য" থাকে (যেমন লগইন অবস্থা, আনরিড নোটিফিকেশন সংখ্যা, ডার্ক মোড চালু আছে কি না) — এগুলো লোকাল স্টেট নাকি অ্যাপ-ওয়াইড স্টেট হওয়া উচিত, এবং কেন?

    এই তিনটিই অ্যাপ-ওয়াইড স্টেটের ভালো উদাহরণ — লগইন অবস্থা প্রায় প্রতিটি স্ক্রিনের আচরণ নির্ধারণ করে, আনরিড সংখ্যা একাধিক জায়গায় (ব্যাজ, তালিকা) একসাথে দেখাতে হয়, আর থিম পুরো অ্যাপ জুড়ে একই থাকতে হয় (L09-এ থিমিং নিয়ে বিস্তারিত)। এগুলো কখনোই শুধু একটি স্ক্রিনের "নিজস্ব" বিষয় নয়।

  2. পরীক্ষা করুন: উপরের দ্বিতীয় কোড সেলে একটি তৃতীয় সাবস্ক্রাইবার যোগ করুন — profile_screen_log = [] এবং cart_store.subscribe(...) দিয়ে এটিকেও একই স্টোরে নিবন্ধন করুন — তারপর কোডটি চালিয়ে দেখুন এটিও বাকি দুটি সাবস্ক্রাইবারের মতোই একই মান পায় কি না।

    হ্যাঁ — _subscribers লিস্টে যত সাবস্ক্রাইবারই থাকুক না কেন, set_state প্রতিবার সবগুলো লিসেনারকে একই নতুন state দিয়ে কল করে। তৃতীয় স্ক্রিনটিও ঠিক একই দুটি মান ([1, 2]) পাবে — স্টোরের সাথে যুক্ত থাকা যেকোনো সংখ্যক স্ক্রিন সবসময় সিঙ্কে থাকে।

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

  • কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
  • Full-Stack Web Frameworks কোর্স সহোদর কোর্স Redux-স্টাইল অপরিবর্তনীয় স্টেট ও ইউনিডাইরেকশনাল ডেটা ফ্লো সেই কোর্সেই ওয়েবের প্রেক্ষাপটে শেখানো হয়েছে।
  • সব 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 — সব এক জায়গায়।
আগের পাঠ
নেভিগেশন স্টেট ও ব্যাক-স্ট্যাক ম্যানেজমেন্ট