MVVM ও ডেটা বাইন্ডিং
এই পাঠে যা শিখবেন
- MVC-এর "পুরো স্ক্রিন রি-রেন্ডার" থেকে MVVM-এর "প্রতি-প্রপার্টি বাইন্ডিং" কীভাবে ভিন্ন
- একটি লগইন স্ক্রিনের জন্য
username,password, ও ডিরাইভডis_login_enabled— তিনটি Observable প্রপার্টি ডিজাইন করা - একটি সত্যিকারের callback-based বাইন্ডিং যেখানে প্রপার্টি বদলালেই View-এর নির্দিষ্ট অংশ নিজে থেকে আপডেট হয়
- কেন মোবাইল প্ল্যাটফর্মে MVVM এত জনপ্রিয় — লাইফসাইকেল-অ্যাওয়্যার বাইন্ডিং টুলের সংক্ষিপ্ত পরিচিতি
১ · প্রতি-প্রপার্টি বাইন্ডিং বনাম পুরো-স্ক্রিন রি-রেন্ডার
FSWF L06-এ দেখানো হয়েছে
Observable ক্লাস কীভাবে একটি মান ধারণ করে ও মান বদলালে সাবস্ক্রাইবারদের জানায়। L10-এর
ProfileController প্রতিটি এডিটের পর পুরো View-কে নিঃশর্তে রি-রেন্ডার করতে বলেছিল। মোবাইল
লগইন স্ক্রিনে এই পদ্ধতি অপচয়ী — ইউজারনেম ফিল্ড বদলালে পাসওয়ার্ড ফিল্ড বা লগইন বাটনকে কেন আবার আঁকতে হবে?
MVVM-এ প্রতিটি Observable প্রপার্টি শুধু তার নিজের UI অংশের সাথে বাইন্ড থাকে।
২ · একটি লগইন স্ক্রিনের MVVM — বাস্তব কোড
নিচের কোডে Observable ক্লাসটি L10-এর মতো নয় — মান আসলেই বদলালেই সাবস্ক্রাইবারদের জানায়
(old_value != new_value চেক)। LoginViewModel-এর is_login_enabled
একটি ডিরাইভড প্রপার্টি — এটি সরাসরি ইউজার-ইনপুট নয়, বরং username ও
password-এর বর্তমান মান থেকে প্রতিবার পুনরায় হিসেব করা হয়।
# ---------- একটি বাইন্ডেবল (observable) প্রপার্টি -- মান সত্যিই বদলালেই জানায় ----------
class Observable:
def __init__(self, initial_value):
self._value = initial_value
self._listeners = []
def get(self):
return self._value
def set(self, new_value):
old_value = self._value
self._value = new_value
if old_value != new_value:
for listener in self._listeners:
listener(new_value)
def subscribe(self, listener):
self._listeners.append(listener)
# ---------- VIEWMODEL -- তিনটি বাইন্ডেবল প্রপার্টি এক্সপোজ করে, View সম্পর্কে কিছুই জানে না ----------
class LoginViewModel:
def __init__(self):
self.username = Observable("")
self.password = Observable("")
self.is_login_enabled = Observable(False)
def set_username(self, value):
self.username.set(value)
self._recompute_login_enabled()
def set_password(self, value):
self.password.set(value)
self._recompute_login_enabled()
def _recompute_login_enabled(self):
enabled = len(self.username.get()) > 0 and len(self.password.get()) >= 6
self.is_login_enabled.set(enabled) # শুধু আসলে বদলালেই বাটন-বাইন্ডিং নোটিফাই হবে
# ---------- VIEW -- প্রতিটি প্রপার্টির সাথে আলাদাভাবে বাইন্ড করে ----------
class LoginView:
def __init__(self, view_model):
self.vm = view_model
self.updates = {"username_field": 0, "password_field": 0, "login_button": 0}
self.vm.username.subscribe(self._on_username_changed)
self.vm.password.subscribe(self._on_password_changed)
self.vm.is_login_enabled.subscribe(self._on_login_enabled_changed)
# প্রাথমিক বাইন্ডিং -- স্ক্রিন খোলার সময় প্রতিটি অংশের প্রথম রেন্ডার
self._on_username_changed(self.vm.username.get())
self._on_password_changed(self.vm.password.get())
self._on_login_enabled_changed(self.vm.is_login_enabled.get())
def _on_username_changed(self, value):
self.updates["username_field"] += 1
print(f"[username ফিল্ড আপডেট #{self.updates['username_field']}] '{value}'")
def _on_password_changed(self, value):
self.updates["password_field"] += 1
masked = "•" * len(value)
print(f"[password ফিল্ড আপডেট #{self.updates['password_field']}] '{masked}' ({len(value)} অক্ষর)")
def _on_login_enabled_changed(self, enabled):
self.updates["login_button"] += 1
state = "সক্রিয় (enabled)" if enabled else "নিষ্ক্রিয় (disabled)"
print(f"[লগইন বাটন আপডেট #{self.updates['login_button']}] {state}")
print("== লগইন স্ক্রিন খোলা হলো ==")
vm = LoginViewModel()
view = LoginView(vm)
print("\n== ইউজার ইউজারনেম টাইপ করলো: 'karim' ==")
vm.set_username("karim") # username_field আপডেট হয়; is_login_enabled এখনো False -- বাটন বদলায় না
print("\n== ইউজার পাসওয়ার্ড টাইপ করলো: '12345' (৫ অক্ষর, যথেষ্ট নয়) ==")
vm.set_password("12345") # password_field আপডেট হয়; is_login_enabled এখনো False -- বাটন বদলায় না
print("\n== ইউজার পাসওয়ার্ড ঠিক করলো: '123456' (৬ অক্ষর) ==")
vm.set_password("123456") # password_field আপডেট হয়; is_login_enabled এখন True -- বাটন আপডেট হয়
print(f"\nমোট আপডেট: {view.updates}")
set_username("karim") ও
set_password("12345")) লগইন বাটন একবারও আপডেট হয় না — কারণ
is_login_enabled-এর মান দুবারই False-ই থাকে (Observable.set()-এর
old_value != new_value চেক ব্যর্থ হয়, তাই কোনো নোটিফিকেশন যায় না)। শুধু তৃতীয় পরিবর্তনে
(পাসওয়ার্ড ৬ অক্ষরে পৌঁছালে) is_login_enabled সত্যিই False → True বদলায়, তাই
বাটন ঠিক তখনই আপডেট হয়। শেষ পর্যন্ত updates: username_field=2
(১টি প্রাথমিক + ১টি পরিবর্তন), password_field=3 (১টি প্রাথমিক + ২টি পরিবর্তন),
login_button=2 (১টি প্রাথমিক + ১টি পরিবর্তন)।
MVVM-এ ViewModel বাইন্ডেবল স্টেট এক্সপোজ করে আর View স্বেচ্ছায় সেই স্টেট "অবজার্ভ" করে — ViewModel কখনো জানে না কে বা কয়টি UI অংশ তার প্রপার্টি বাইন্ড করে রেখেছে। এই ডিকাপলিং আর প্রতি-প্রপার্টি নির্ভুল আপডেট ছোট মোবাইল স্ক্রিনে (সীমিত ব্যাটারি/CPU) MVC-এর নিঃশর্ত রি-রেন্ডারের চেয়ে বেশি কার্যকর — এবং এটিই কেন Android/iOS-এর নেটিভ ডেটা-বাইন্ডিং টুল এই একই ধারণার ওপর ভিত্তি করে তৈরি।
ভাবনার প্রশ্ন
প্রতিটি প্রশ্ন নিজে কিছুক্ষণ ভাবুন — তারপর "→ উত্তর" চাপুন।
প্র ০১
is_login_enabled-কে কেন একটি "ডিরাইভড" প্রপার্টি বলা হচ্ছে?
কারণ ইউজার সরাসরি এর মান টাইপ করে না — এটি প্রতিবার username বা password
বদলালে _recompute_login_enabled()-এর মাধ্যমে সেই দুটি প্রপার্টির বর্তমান মান থেকে নতুন
করে হিসেব করা হয়। ডিরাইভড প্রপার্টি ব্যবহার করলে "লগইন বাটন কখন সক্রিয় হবে" এই নিয়মটি একটিমাত্র জায়গায়
(_recompute_login_enabled) থাকে — View-এর কোনো অংশে এই লজিক ছড়িয়ে থাকে না।
প্র ০২ L10-এর MVC আর এই পাঠের MVVM — দুটোই তো Controller/ViewModel-এ লজিক রাখে। আসল পার্থক্যটা তাহলে কোথায়?
পার্থক্যটা View কীভাবে জানে কখন রি-রেন্ডার করতে হবে তাতে। MVC-তে Controller-কে সক্রিয়ভাবে
view.render() কল করতে হয় — ভুলে গেলে UI stale থেকে যায় (L10-এর অনুশীলনে দেখা গেছে)। MVVM-এ
View নিজেই ViewModel-এর প্রপার্টি অবজার্ভ করে — ViewModel কখনো View-কে সরাসরি কল করে না,
বরং শুধু মান বদলালেই সাবস্ক্রাইবাররা স্বয়ংক্রিয়ভাবে জানতে পারে। এটি "push" (Controller ঠেলে দেয়) বনাম
"observe" (View নিজে দেখে) — এই দুই ভিন্ন মডেল।
প্র ০৩
যদি Observable.set()-এ old_value != new_value চেকটি না থাকতো, তাহলে উপরের কোডে মোট কতগুলো বাটন-আপডেট হতো?
তিনটি পরিবর্তনের প্রতিটিতেই _recompute_login_enabled() চলে এবং প্রতিবারই
is_login_enabled.set(...) কল হয় — চেক না থাকলে প্রতিটি কলই নোটিফাই করতো, ফলে প্রাথমিক
রেন্ডারসহ মোট ৪টি বাটন-আপডেট হতো (এখন যেখানে মাত্র ২টি হয়) — যদিও বাস্তবে বাটনের অবস্থা মাত্র একবারই
সত্যিকারভাবে বদলেছে (disabled → enabled)। এটিই দেখায় কেন সমতা-চেকটি অপ্রয়োজনীয় UI কাজ এড়াতে জরুরি।
অনুশীলন
-
চিন্তা করুন: বাস্তব একটি লগইন স্ক্রিনে "পাসওয়ার্ড দেখাও/লুকাও" (show/hide password) টগল
বাটন থাকে। এটিকে কীভাবে
LoginViewModel-এ একটি নতুন Observable প্রপার্টি হিসেবে যোগ করবেন বলে মনে হয়?একটি নতুন
self.is_password_visible = Observable(False)প্রপার্টি ও একটিtoggle_password_visibility()মেথড যোগ করা যেতে পারে যাself.is_password_visible.set(not self.is_password_visible.get())কল করবে। View-এ এই প্রপার্টির একটি নতুন সাবস্ক্রাইবার থাকবে যা পাসওয়ার্ড ফিল্ডের masking (•বনাম আসল অক্ষর) টগল করবে — লক্ষণীয় যে এটি সম্পূর্ণ স্বাধীন একটি প্রপার্টি,username/password-এর সাথে সরাসরি সম্পর্কহীন, তাই এটি বদলালে শুধু masking-বাইন্ডিং আপডেট হবে, username/password ফিল্ড বা লগইন বাটন নয়। -
পরীক্ষা করুন: উপরের কোড সেলে সবশেষে
vm.set_username("")— অর্থাৎ ইউজারনেম খালি করে দিন — এই লাইনটি যোগ করে আবার চালান, এবং দেখুনupdates["login_button"]-এর চূড়ান্ত মান কত হয়।vm.set_username("")কল হলেusername"karim" থেকে "" -এ বদলায় (username_fieldআপডেট হয়), এবং_recompute_login_enabled()আবার চলে:len("") > 0এখনFalse, তাইenabledহয়ে যায়False— যেহেতু আগের মানTrueছিল, এটি একটি প্রকৃত পরিবর্তন, তাইlogin_buttonআবার আপডেট হয়েupdates["login_button"]২ থেকে ৩-এ পৌঁছাবে (disabled দেখিয়ে)।
আরও পড়ুন · ABCL TECH-এ আপনার পরবর্তী পদক্ষেপ
- কোর্সের সম্পূর্ণ সিলেবাস দেখুন ৫৭টি পাঠ অ্যাপ লাইফসাইকেল, মোবাইল UI/UX, MVVM/MVI আর্কিটেকচার, নেটিভ বনাম ক্রস-প্ল্যাটফর্ম, নেভিগেশন, স্টেট ম্যানেজমেন্ট, লোকাল স্টোরেজ, ডিভাইস ফিচার ও ডিপ্লয়মেন্ট — বাকি পাঠগুলো শীঘ্রই যুক্ত হবে।
- FSWF · MVVM and MVP Patterns সহোদর পাঠ Observable প্যাটার্নের সাধারণ সংজ্ঞা ও MVP-এর সাথে তুলনা এই পাঠেই তৈরি হয়েছে।
- সব 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 — সব এক জায়গায়।