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

অ্যাপ কিলের পর স্টেট রিস্টোরেশন

State restoration across app kills
৭ মিনিট পড়া মধ্যম · Intermediate Python কোডসহ সম্পূর্ণ বাংলায়

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

  • কেন killed স্টেট মেমোরির সাধারণ ডেটা ধ্বংস করে দেয়, এবং কেন এটি প্রত্যাশিত আচরণ
  • L01-এর AppLifecycle ও একটি সাধারণ key-value স্টোর — এই দুটি প্যাটার্ন একসাথে ব্যবহার করা
  • background-এ যাওয়ার মুহূর্তে স্টেট সেভ করা, রিলঞ্চের পর তা পুনরুদ্ধার করা
  • একটি genuine dict-equality চেক দিয়ে প্রমাণ করা যে পুনরুদ্ধার করা ডেটা মূল ডেটার সাথে হুবহু মেলে

১ · সমস্যা: killed মানে মেমোরি খালি

L01-এ দেখা AppLifecycle-এর ALLOWED টেবিলে killed ছিল একটি টার্মিনাল স্টেট — সেখান থেকে কোনো বৈধ ট্রানজিশন নেই। বাস্তবে এর মানে: OS যখন একটি অ্যাপ প্রসেস সম্পূর্ণ বন্ধ করে দেয়, তখন সেই প্রসেসের মেমোরিতে থাকা প্রতিটি সাধারণ ভ্যারিয়েবল — একটি অসম্পূর্ণ ফর্মের টেক্সট, স্ক্রল পজিশন, বা "কোন ট্যাব সিলেক্টেড ছিল" — সব হারিয়ে যায়। অ্যাপ আবার চালু হলে সেটি একটি সম্পূর্ণ নতুন প্রসেস, পুরনো মেমোরির কোনো অ্যাক্সেস ছাড়াই।

সমস্যা হলো — একটি অ্যাপ কখনো আগে থেকে নিশ্চিতভাবে জানে না ঠিক কখন OS তাকে kill করবে (এটি background-এ কয়েক সেকেন্ড পরেও হতে পারে, আবার কয়েক ঘণ্টা পরেও)। তাই সমাধান হলো — background-এ যাওয়ার মুহূর্তেই, kill হওয়ার জন্য অপেক্ষা না করে, প্রাসঙ্গিক স্টেট একটি স্থায়ী (persistent) জায়গায় সেভ করে রাখা।

foreground background killed KV store (ডিস্কে অনুকরণ) সেভ করা হলো রিলঞ্চ not_running → foreground পুনরুদ্ধার
background-এ যাওয়ার মুহূর্তেই স্টেট KV স্টোরে সেভ হয় — killed অবস্থায় ঠিক কখন হবে তার জন্য অপেক্ষা করা হয় না; রিলঞ্চের পর সেই সেভ করা ডেটা পড়ে UI পুনর্গঠিত হয়।

২ · AppLifecycle ও একটি সাধারণ Key-Value স্টোর

নিচে L01-এর AppLifecycle-এর সরলীকৃত সংস্করণ, আর পাশে একটি সাধারণ KeyValueStore — শুধু get/set/remove মেথডসহ একটি dict-এর মোড়ক। এটি M8/L32-এ পূর্ণাঙ্গভাবে শেখানো হবে; এখানে শুধু স্টেট রিস্টোরেশনের জন্য যতটুকু দরকার ততটুকু।

Python
# L01-এর AppLifecycle-এর সরলীকৃত সংস্করণ
class AppLifecycle:
    ALLOWED = {
        'not_running': {'foreground'},
        'foreground':  {'background'},
        'background':  {'foreground', 'killed'},
        'killed':      set(),
    }

    def __init__(self):
        self.state = 'not_running'

    def transition(self, new_state, reason):
        if new_state in self.ALLOWED[self.state]:
            old_state = self.state
            self.state = new_state
            print(f"{reason:44s} | {old_state} -> {new_state}  (বৈধ)")
            return True
        print(f"{reason:44s} | {self.state} -> {new_state}  (অবৈধ)")
        return False


# একটি সাধারণ key-value স্টোর -- শুধু get/set/remove সহ একটি dict-এর মোড়ক
# বাস্তব ডিভাইসে এটি ডিস্কে (SharedPreferences/UserDefaults-স্টাইল) পার্সিস্ট হতো,
# এই Pyodide স্যান্ডবক্স শুধু মেমোরিতে এটি অনুকরণ করতে পারে (L32-এ বিস্তারিত)
class KeyValueStore:
    def __init__(self):
        self._data = {}

    def get(self, key, default=None):
        return self._data.get(key, default)

    def set(self, key, value):
        self._data[key] = value

    def remove(self, key):
        self._data.pop(key, None)

    

৩ · সম্পূর্ণ প্রবাহ: সেভ, কিল, রিলঞ্চ, রিস্টোর

এবার একটি "যোগাযোগ ফর্ম" স্ক্রিনের পুরো জীবনচক্র সিমুলেট করা হবে — ইউজার টাইপ করছে, হঠাৎ ব্যাকগ্রাউন্ডে চলে যাওয়া, OS দ্বারা kill হওয়া, এবং শেষে রিলঞ্চ করে ডেটা পুনরুদ্ধার। শেষে একটি genuine equality চেক দিয়ে প্রমাণ করা হবে পুনরুদ্ধার করা ডেটা কিলের ঠিক আগে সেভ করা স্ন্যাপশটের সাথে হুবহু মেলে।

Python
kv_store = KeyValueStore()
app = AppLifecycle()

app.transition('foreground', 'অ্যাপ চালু করা হলো')

# ইউজার একটি যোগাযোগ ফর্ম পূরণ করছে -- এই মুহূর্তে এটি শুধু মেমোরিতে আছে
contact_form = {"name": "করিম", "email": "karim@example.com", "message": "আমি একটি ডেমো বুক করতে চাই।"}
print(f"ইউজার ফর্মে টাইপ করছে (শুধু মেমোরিতে): {contact_form}")

app.transition('background', 'ইউজার হঠাৎ একটি ফোন কল রিসিভ করলো')

# background-এ যাওয়ার মুহূর্তেই ফর্মের একটি স্বতন্ত্র স্ন্যাপশট KV store-এ সেভ করা হলো
saved_snapshot = dict(contact_form)   # মূল contact_form থেকে সম্পূর্ণ আলাদা একটি কপি
kv_store.set("contact_form_draft", saved_snapshot)
print(f"KV store-এ সেভ করা হলো: {kv_store.get('contact_form_draft')}")

app.transition('killed', 'OS মেমোরি খালি করতে অ্যাপ প্রসেসটি সম্পূর্ণ বন্ধ করে দিলো')

# প্রসেস kill হওয়ার অর্থ -- সাধারণ মেমোরির ভ্যারিয়েবল (contact_form) হারিয়ে যায়
del contact_form
print("অ্যাপ প্রসেস kill হয়ে গেছে -- মেমোরির contact_form ভ্যারিয়েবলটি আর অস্তিত্বে নেই।")
print(f"কিন্তু kv_store এখনো টিকে আছে (বাস্তব ডিভাইসে এটি ডিস্কে থাকত): {kv_store.get('contact_form_draft')}")

# --- ইউজার আবার অ্যাপ আইকনে ট্যাপ করলো -- সম্পূর্ণ নতুন প্রসেস, killed থেকে সরাসরি ট্রানজিশন নয় ---
relaunched_app = AppLifecycle()
relaunched_app.transition('foreground', 'ইউজার আবার অ্যাপ চালু করলো')

restored_form_data = kv_store.get("contact_form_draft", {})
print(f"\nরিলঞ্চের পর KV store থেকে পুনরুদ্ধার করা ফর্ম-ডেটা: {restored_form_data}")

is_identical = restored_form_data == saved_snapshot
print(f"\nপ্রমাণ -- পুনরুদ্ধার করা dict কিলের ঠিক আগে সেভ করা স্ন্যাপশটের সাথে হুবহু সমান: {is_identical}")
assert is_identical
print("assert ব্যর্থ হয়নি -- UI এখন ঠিক যেখানে ইউজার রেখে গিয়েছিল, সেখান থেকেই আবার পূরণ করা দেখাতে পারবে।")

    
saved_snapshot = dict(contact_form) একটি স্বতন্ত্র কপি তৈরি করে — মূল contact_form পরে বদলে গেলেও (বা এই ক্ষেত্রে সম্পূর্ণ মুছে গেলেও, del-এর মাধ্যমে) সেভ করা স্ন্যাপশট অপরিবর্তিত থাকে। এই স্যান্ডবক্সে KeyValueStore শুধু মেমোরিতে কাজ করে (তাই একই Python প্রসেসের ভেতরে ডেটা "টিকে" থাকে) — বাস্তব ডিভাইসে এই একই ডেটা del-এর পরও, এমনকি সম্পূর্ণ প্রসেস বন্ধ হওয়ার পরও ডিস্কে টিকে থাকত, যা এই একটি Python সেশনের সীমাবদ্ধতার মধ্যে হুবহু দেখানো সম্ভব নয়।
মূল কথা · Key takeaway

killed স্টেট থেকে কখনো সরাসরি ফিরে আসা যায় না — কিন্তু একটি ভালো অ্যাপ তা প্রয়োজনও মনে করে না, কারণ সে background-এ যাওয়ার মুহূর্তেই তার প্রাসঙ্গিক স্টেট key-value স্টোরে সেভ করে রেখেছিল। রিলঞ্চের পর সেই স্টোর থেকে পড়েই UI পুনর্গঠিত হয় — ইউজারের কাছে মনে হয় অ্যাপটি কখনো বন্ধই হয়নি। M8 (L32 থেকে) এই key-value স্টোর প্যাটার্নটি আরও বিস্তারিতভাবে কভার করবে।

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

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

প্র ০১ স্টেট সেভ করার কাজটি কি killed-এ যাওয়ার সময় করলে যথেষ্ট নয়, কেন background-এ যাওয়ার সময়েই করতে হয়?

কারণ কোনো অ্যাপ কোডকে OS killed-এ যাওয়ার আগে কোনো নোটিফিকেশন বা সুযোগ দেয় না — কিল হওয়া মানেই প্রসেসটি হঠাৎ সম্পূর্ণ বন্ধ হয়ে যাওয়া, কোনো "শেষ মুহূর্তের কোড চালানোর" সুযোগ ছাড়াই। তাই একমাত্র নির্ভরযোগ্য মুহূর্ত হলো background-এ যাওয়ার সময়, যেটি একটি অ্যাপ নিশ্চিতভাবে জানতে পারে এবং তখনই তার সেভ-করার কোড চালাতে পারে।

প্র ০২ যদি saved_snapshot = dict(contact_form)-এর বদলে সরাসরি kv_store.set("contact_form_draft", contact_form) করা হতো (কপি ছাড়া), তাহলে কি ফলাফল বদলে যেত?

এই নির্দিষ্ট উদাহরণে ফলাফল একই থাকত, কারণ contact_form সেভ করার পরে আর কোনো ফিল্ড পরিবর্তন করা হয়নি, শুধু del দিয়ে ভ্যারিয়েবলের রেফারেন্স মোছা হয়েছে (যা dict অবজেক্টটিকে ধ্বংস করে না, যতক্ষণ kv_store-এর ভেতরে আরেকটি রেফারেন্স আছে)। কিন্তু বাস্তব প্র্যাকটিসে সবসময় একটি স্বতন্ত্র কপি সেভ করা নিরাপদ অভ্যাস — যদি ইউজার ব্যাকগ্রাউন্ডে যাওয়ার ঠিক পরেই ফর্মে আরও কিছু টাইপ করত (কিছু OS-এ সংক্ষিপ্ত গ্রেস পিরিয়ডে সম্ভব), কপি না থাকলে সেভ করা স্ন্যাপশটও অজান্তেই বদলে যেত।

প্র ০৩ উপরের কোড সেলে restored_form_data = kv_store.get("contact_form_draft", {})-এ দ্বিতীয় আর্গুমেন্ট {} কেন দেওয়া হয়েছে?

এটি একটি ডিফল্ট মান — যদি কখনো "contact_form_draft" কী-টি store-এ না থাকত (যেমন ইউজার কখনো ফর্মে কিছু টাইপই না করে থাকলে, তাই কোনো সেভও হয়নি), তাহলে get() একটি খালি dict ফেরত দিত ক্র্যাশ করার বদলে — L32-এ এই ডিফল্ট-ভ্যালু ফলব্যাক প্যাটার্নটি বিস্তারিতভাবে দেখানো হবে।

অনুশীলন

  1. চিন্তা করুন: একটি ভিডিও-প্লেয়ার অ্যাপে "ভিডিওর ঠিক কোন সেকেন্ডে ছিল" রিস্টোর করার জন্য কী কী তথ্য key-value স্টোরে সেভ করা দরকার বলে আপনার মনে হয়? শুধু একটি সংখ্যা যথেষ্ট, নাকি আরও কিছু?

    শুধু "সেকেন্ড" সংখ্যাটি যথেষ্ট নয় — কোন ভিডিওটি চলছিল তার আইডি/URL-ও লাগবে, প্লেব্যাক স্পিড লাগতে পারে, এবং হয়তো "অটো-প্লে চালু ছিল কি না"ও। বাস্তবে এই পুরো "প্লেব্যাক অবস্থা"-কে একটি একক dict হিসেবে সেভ করাই স্বাভাবিক — ঠিক এই পাঠের contact_form-এর মতোই, একাধিক ফিল্ড একসাথে একটি কী-এর নিচে।

  2. পরীক্ষা করুন: উপরের দ্বিতীয় কোড সেলে app.transition('background', ...)-এর ঠিক পরে, kv_store.set(...) কল করার আগে সরাসরি app.transition('killed', 'দ্রুত মেমোরি সংকট') কল করুন (অর্থাৎ সেভ করার ধাপটি বাদ দিন) — তারপর রিলঞ্চের পর restored_form_data কী দেখায় তা লক্ষ্য করুন।

    সেভ ধাপ বাদ দিলে kv_store.get("contact_form_draft", {}) ডিফল্ট খালি dict {} ফেরত দেবে — কারণ সেই কী-টি store-এ কখনোই সেট করা হয়নি। is_identical চেক তখন False হবে এবং assert ব্যর্থ হবে — এটিই দেখায় কেন background-এ যাওয়ার মুহূর্তে সেভ করাটা ঐচ্ছিক নয়, বরং বাধ্যতামূলক একটি ধাপ।

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

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