MVVM (Model-View-ViewModel) একটি আর্কিটেকচারাল প্যাটার্ন যেখানে ViewModel Presenter-কে প্রতিস্থাপন করে এবং View-এর সাথে যোগাযোগের জন্য রিঅ্যাকটিভ মেকানিজম ব্যবহার করে: SwiftUI-তে ObservableObject, Android-এ LiveData/StateFlow। ViewModel-এর View-এর কোনো রেফারেন্স নেই — ডেটা সাবস্ক্রিপশনের মাধ্যমে পাস করা হয়, যা ViewContract ইন্টারফেসের প্রয়োজনীয়তা দূর করে এবং টেস্টিং আরও সহজ করে তোলে। Apple 2019 সাল থেকে SwiftUI-র সঙ্গে MVVM সুপারিশ করে, Google Jetpack-এর সঙ্গে MVVM-কে Android-এর অফিসিয়াল আর্কিটেকচার হিসেবে সুপারিশ করে। আরও জানুন Android Architecture Guide-এ।
মুখ্য বিষয়
MVVM (Model-View-ViewModel) একটি আর্কিটেকচারাল প্যাটার্ন যা জন গসম্যান 2005 সালে Microsoft-এর Windows Presentation Foundation (WPF)-এর জন্য বর্ণনা করেছিলেন। ViewModel কেন্দ্রীয় উপাদান যা স্ক্রিনের স্টেট ও বিজনেস লজিক ধারণ করে কিন্তু View-এর কোনো রেফারেন্স নেই। ডেটা রিঅ্যাকটিভ বাইন্ডিং মেকানিজমের মাধ্যমে পাস করা হয়: View ViewModel-এর পরিবর্তনে সাবস্ক্রাইব করে এবং ডেটা পরিবর্তনে স্বয়ংক্রিয়ভাবে পুনরায় রেন্ডার হয়।
MVVM ও MVP-র মধ্যে মূল পার্থক্য — ViewContract-এর অনুপস্থিতি। MVP-তে, Presenter view.showUser(data) মেথড কল করে, অর্থাৎ Presenter সক্রিয়ভাবে View-তে ডেটা "ঠেলে" দেয়। MVVM-তে, View নিজেই সাবস্ক্রিপশনের মাধ্যমে ViewModel থেকে ডেটা "টানে": ViewModel জানে না তার কোনো সাবস্ক্রাইবার আছে কিনা। এটি বিচ্ছিন্ন View সমস্যা দূর করে — যদি রোটেশনে Activity ধ্বংস হয়, ViewModel কাজ চালিয়ে যায়, এবং নতুন Activity কেবল বর্তমান ডেটায় সাবস্ক্রাইব করে। IT Sectr-এ, আমরা 2020 সাল থেকে সব নতুন প্রজেক্টে MVVM ব্যবহার করছি — কোড আরও পূর্বানুমানযোগ্য হয়েছে, টেস্ট আরও স্থিতিশীল হয়েছে।
| উপাদান | দায়িত্ব | প্ল্যাটফর্ম |
|---|---|---|
| Model | ডেটা, বিজনেস লজিক, রিপোজিটরি | Android/iOS |
| View | প্রদর্শন, ViewModel-এ সাবস্ক্রিপশন | Activity/Composable, UIView/SwiftUI View |
| ViewModel | স্ক্রিন স্টেট, লজিক, নেভিগেশন | ViewModel (Jetpack), ObservableObject |
রিঅ্যাকটিভ বাইন্ডিং — MVVM-এর ভিত্তি। Android-এ, LiveData (Jetpack-এর অংশ) একটি পর্যবেক্ষণযোগ্য ডেটা ধারক। Activity observe()-এর মাধ্যমে সাবস্ক্রাইব করে: viewModel.user.observe(this) { user -> binding.name.text = user.name }। যখন user পরিবর্তিত হয়, সকল সাবস্ক্রাইবার স্বয়ংক্রিয়ভাবে নতুন মান পায়। iOS-এ, SwiftUI ViewModel-এ @Published প্রপার্টি ব্যবহার করে — পরিবর্তনগুলি স্বয়ংক্রিয়ভাবে View পুনরায় রেন্ডার করে। এটি MVP-তে প্রয়োজনীয় ম্যানুয়াল showUser/hideLoading কল দূর করে।
Jetpack থেকে ViewModel — MVVM বাস্তবায়নের জন্য Google-এর অফিসিয়াল উপাদান। ViewModel স্ক্রিন রোটেশন টিকে থাকে: কনফিগারেশন পরিবর্তনে Activity ধ্বংস ও পুনরায় তৈরি হয়, কিন্তু ViewModel মেমোরিতে থাকে। নতুন Activity ইনস্ট্যান্স ViewModelProvider-এর মাধ্যমে একই ViewModel পায়। ViewModel-এর Activity, Context বা View-এর কোনো রেফারেন্স নেই — এটি পরিষ্কার এবং Robolectric ছাড়াই ইউনিট টেস্টে পরীক্ষাযোগ্য।
// StateFlow-সহ ViewModel — MVVM-এর আধুনিক বাস্তবায়ন
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _state = MutableStateFlow<UserState>(UserState.Loading)
val state: StateFlow<UserState> = _state.asStateFlow()
fun loadUser(userId: Int) {
viewModelScope.launch {
_state.value = UserState.Loading
repository.getUser(userId)
.onSuccess { user ->
_state.value = UserState.Success(user)
}
.onFailure { e ->
_state.value = UserState.Error(e.message ?: "Unknown")
}
}
}
}
sealed interface UserState {
data object Loading : UserState
data class Success(val user: User) : UserState
data class Error(val message: String) : UserState
}
// View (Activity) state-এ সাবস্ক্রাইব করে
class UserActivity : AppCompatActivity() {
private val viewModel: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
viewModel.state.onEach { state ->
when (state) {
is UserState.Loading -> /* লোডিং দেখান */
is UserState.Success -> /* ডেটা প্রদর্শন করুন */
is UserState.Error -> /* ত্রুটি দেখান */
}
}.launchIn(lifecycleScope)
viewModel.loadUser(42)
}
}
LiveData বনাম StateFlow — LiveData (2017) — Jetpack-এর প্রথম রিঅ্যাকটিভ উপাদান, Activity লাইফসাইকেলের জন্য অপ্টিমাইজড: onStop-এ স্বয়ংক্রিয় আনসাবস্ক্রাইব। StateFlow (2021) — Kotlin Flow বাস্তবায়ন, লাইফসাইকেলের সাথে আবদ্ধ নয়, কিন্তু lifecycleScope-এর মাধ্যমে ম্যানুয়াল আনসাবস্ক্রাইব প্রয়োজন। StateFlow coroutines, concat, map ও অন্যান্য Flow অপারেটর সমর্থন করে, যা LiveData-তে নেই। IT Sectr-এ, আমরা সব নতুন ViewModel-এর জন্য StateFlow ব্যবহার করি — এটি ছোট, আরও শক্তিশালী এবং coroutines-এর সাথে ভালোভাবে সংহত হয়।
DataBinding ও ViewBinding — DataBinding ViewModel-কে XML-এর সাথে @{viewModel.user.name} দিয়ে সরাসরি লেআউটে বাঁধে, Activity-তে কোড দূর করে। ViewBinding ভিউ অ্যাক্সেসের জন্য টাইপ-নিরাপদ ক্লাস জেনারেট করে। Google সরল প্রজেক্টের জন্য ViewBinding এবং জটিল ডেটা বাইন্ডিংয়ের প্রজেক্টের জন্য DataBinding সুপারিশ করে। Jetpack Compose-এ, DataBinding প্রয়োজন নেই — @Composable ফাংশন State পরিবর্তনে স্বয়ংক্রিয়ভাবে পুনরায় রেন্ডার হয়।
iOS-এ MVVM Combine থেকে ObservableObject-এর মাধ্যমে বাস্তবায়িত হয়। ViewModel একটি ক্লাস যা ObservableObject থেকে ইনহেরিট করে, @Published ফিল্ড সহ। SwiftUI View @ObservedObject বা @StateObject-এর মাধ্যমে ViewModel-এ সাবস্ক্রাইব করে। যখন কোনো @Published প্রপার্টি পরিবর্তিত হয়, SwiftUI স্বয়ংক্রিয়ভাবে সেই View পুনরায় রেন্ডার করে যা এই প্রপার্টির ওপর নির্ভরশীল। Apple 2019 সালে WWDC-তে SwiftUI Combine-সহ উপস্থাপন করেছিল — তারপর থেকে MVVM iOS-এর জন্য অফিসিয়ালভাবে সুপারিশকৃত প্যাটার্নে পরিণত হয়েছে।
import SwiftUI
import Combine
// ViewModel — @Published ফিল্ড-সহ ObservableObject
final class UserViewModel: ObservableObject {
@Published private(set) var state: UserState = .loading
private let service: UserService
init(service: UserService) {
self.service = service
}
func loadUser(id: Int) {
state = .loading
service.fetchUser(id: id) { [weak self] result in
guard let self else { return }
switch result {
case .success(let user):
self.state = .success(user)
case .failure(let error):
self.state = .error(error.localizedDescription)
}
}
}
}
enum UserState {
case loading
case success(User)
case error(String)
}
// SwiftUI View — ViewModel-এ সাবস্ক্রাইব করে
struct UserView: View {
@StateObject private var viewModel: UserViewModel
var body: some View {
switch viewModel.state {
case .loading:
ProgressView()
case .success(let user):
VStack {
Text(user.name).font(.title)
Text(user.email).font(.body)
}
case .error(let message):
Text(message).foregroundColor(.red)
}
}
}
@StateObject বনাম @ObservedObject — @StateObject ViewModel তৈরি করে এবং এর লাইফসাইকেল পরিচালনা করে (View-এর জীবনকালে একবার)। @ObservedObject — ViewModel বাইরে থেকে তৈরি হয় এবং View-তে পাস করা হয়। WWDC 2022 তৈরির জন্য @StateObject এবং Views-এর মধ্যে ViewModel পাসের জন্য @ObservedObject সুপারিশ করে। iOS 17 (2023)-এ, @Observable ম্যাক্রো এসেছে — যা সাবস্ক্রিপশন অটোমেট করে এবং @Published অ্যানোটেশন দূর করে। @Observable হলো Combine-এর বিবর্তন, যা iOS ডেভেলপমেন্টকে Kotlin Flow রিঅ্যাকটিভিটির কাছাকাছি নিয়ে আসে।
UIKit + MVVM — UIKit প্রজেক্টের জন্য (SwiftUI ছাড়া), MVVM Combine ও @Published-এর মাধ্যমে sink()-সহ UIViewController-এ সাবস্ক্রিপশন নিয়ে বাস্তবায়িত হয়। ViewModel একই, View @Published-এ সাবস্ক্রিপশন-সহ UIViewController। Combine iOS 13 (2019) থেকে উপলব্ধ এবং সিস্টেমে নির্মিত — কোনো অতিরিক্ত ডিপেন্ডেন্সি প্রয়োজন নেই। Apple Developer Survey (2025) অনুসারে, 45% iOS প্রজেক্ট UIKit-এর সাথেও Combine ব্যবহার করে, 35% SwiftUI + Combine ব্যবহার করে, 20% RxSwift (লিগ্যাসি) ব্যবহার করে।
MVVM MVP-র চেয়ে ভালো তিনটি মূল দিকে: ViewContract ইন্টারফেসের অনুপস্থিতি, স্বয়ংক্রিয় সাবস্ক্রিপশন ব্যবস্থাপনা, ও স্ক্রিন রোটেশন টিকে থাকা। MVP-তে, প্রতিটি স্ক্রিনের জন্য ViewContract ইন্টারফেস + Presenter ক্লাস + onStart/onStop-এ সাবস্ক্রিপশন/আনসাবস্ক্রিপশন প্রয়োজন। MVVM-তে, শুধু ViewModel তৈরি হয় — Activity-তে সাবস্ক্রিপশন ম্যানুয়াল detach() ছাড়া observe()-এর মাধ্যমে হয়।
| মাপকাঠি | MVP | MVVM |
|---|---|---|
| ViewContract ইন্টারফেস | প্রতি স্ক্রিনে ১টি | প্রয়োজন নেই |
| সাবস্ক্রিপশন ব্যবস্থাপনা | ম্যানুয়াল attach/detach | স্বয়ংক্রিয় (lifecycle-aware) |
| স্ক্রিন রোটেশন | Retain-fragment | ViewModel রোটেশন টিকে থাকে |
| টেস্টিং | Mock ViewContract | নির্ভরতাহীন পরিষ্কার ক্লাস |
| রিঅ্যাকটিভিটি | Presenter-এ কলব্যাক | LiveData/StateFlow/Combine |
MVVM-এর অসুবিধা — রিঅ্যাকটিভ চেইন ডিবাগিংয়ের জটিলতা ও ভুল সাবস্ক্রিপশনে মেমোরি লিকের ঝুঁকি। LiveData লাইফসাইকেল নিরাপত্তা সমাধান করে, StateFlow-এর lifecycleScope প্রয়োজন, Combine-এর AnyCancellable-সহ sink প্রয়োজন। MVP-তে, সব কল স্পষ্ট (view.showUser), MVVM-তে ডেটা রিঅ্যাকটিভ স্ট্রিমের মাধ্যমে আসে — ট্রেসিংয়ের জন্য subscribe ক্লোজারে ডিবাগ ব্রেকপয়েন্ট প্রয়োজন। একাধিক StateFlow-সহ বড় ViewModel-এ, UI আপডেট মিস হতে পারে যদি View নির্দিষ্ট Flow-এ সাবস্ক্রাইব না করে।
যখন MVP এখনও ভালো — API 21 (Android 5)-এর নিচে ন্যূনতম Android ভার্সনের প্রজেক্টে, যেখানে Jetpack ViewModel AndroidX ছাড়া উপলব্ধ নয়, এবং Combine ছাড়া বিশুদ্ধ UIKit (iOS 12 ও নিচে) ব্যবহার করা প্রজেক্টে। লিগ্যাসি প্রজেক্টের জন্য যেখানে পুরো কোডবেস আগে থেকেই MVP-তে, MVVM-এ পূর্ণ রূপান্তর সবসময় ন্যায্য নয় — সার্ভিসে লজিকের ধীরে ধীরে স্থানান্তর-সহ MVP বজায় রাখা ৩ মাসে ১০০ স্ক্রিন পুনর্লিখনের চেয়ে সস্তা।
ViewModel ইউনিট টেস্টে পরীক্ষা করা হয় প্ল্যাটফর্ম নির্ভরতা ছাড়া — এটি MVVM-এর পক্ষে প্রধান যুক্তি। Android-এ, ViewModel-এ Activity, Context বা View থাকে না — সব নির্ভরতা (Repository, UseCase) কনস্ট্রাক্টরের মাধ্যমে পাস হয় এবং মক অবজেক্ট দিয়ে প্রতিস্থাপিত হয়। iOS-এ, ObservableObject অ্যাপ্লিকেশন চালু না করেই XCTest-এর মাধ্যমে পরীক্ষা করা হয়, যা স্থিতিশীলতা ও টেস্ট এক্সিকিউশন গতি প্রদান করে।
// MockK-সহ Android ViewModel ইউনিট টেস্ট
class UserViewModelTest {
private val repository = mockk<UserRepository>()
private val viewModel = UserViewModel(repository)
@Test
fun loadUser_success_updatesState() = runTest {
val user = User(1, "John", "john@test.com")
coEvery { repository.getUser(1) } returns Result.success(user)
viewModel.loadUser(1)
assertEquals(UserState.Success(user), viewModel.state.value)
}
@Test
fun loadUser_error_updatesErrorState() = runTest {
val error = RuntimeException("Network error")
coEvery { repository.getUser(1) } returns Result.failure(error)
viewModel.loadUser(1)
val state = viewModel.state.value
assertTrue(state is UserState.Error)
assertEquals("Network error", (state as UserState.Error).message)
}
}
iOS ViewModel একইভাবে পরীক্ষা করা হয়: mock UserService ইনজেক্ট করুন, loadUser কল করুন, XCTestExpectation-এর মাধ্যমে স্টেট চেক করুন। Combine Publisher XCTestCase-এর মাধ্যমে wait(for: expectations, timeout: 1.0)-সহ পরীক্ষা করা হয়। UserState স্ট্রাকচার — অ্যাসোসিয়েটেড ভ্যালু-সহ enum — অপারেশনের পরে স্ক্রিনের সঠিক স্টেট চেক করতে দেয়।
কোড কভারেজ IT Sectr প্রজেক্টে MVVM ব্যবহার করে ViewModel ও Repository-র জন্য 75–90%। ViewModel ইউনিট টেস্টে আচ্ছাদিত, Repository টেস্ট ডাটাবেস-সহ ইন্টিগ্রেশন টেস্টে। SwiftUI ও Jetpack Compose-এ View গুরুত্বপূর্ণ পরিস্থিতির জন্য UI টেস্টে (XCUITest, Compose Test) পরীক্ষা করা হয়। বাকি UI স্ক্রিনশট টেস্টে (Snapshot Testing) চেক করা হয় — এটি UI টেস্টের চেয়ে দ্রুত এবং প্রদর্শন সঠিকতায় 95% আস্থা দেয়।
সচরাচর জিজ্ঞাসা
MVVM-এ, ViewModel-এর View-এর কোনো রেফারেন্স নেই — ডেটা রিঅ্যাকটিভ মেকানিজমের (LiveData, StateFlow, @Published) মাধ্যমে পাস হয়। MVP-তে, Presenter ViewContract ইন্টারফেসের মাধ্যমে সরাসরি View মেথড কল করে। MVVM ViewContract ও ম্যানুয়াল attach/detach দূর করে, কিন্তু রিঅ্যাকটিভ স্ট্রিম বোঝার প্রয়োজন। ViewModel Android-এ স্ক্রিন রোটেশন টিকে থাকে, Presenter-এর retain-fragment প্রয়োজন।
সর্বনিম্ন সেট: lifecycle-viewmodel-ktx (ViewModel), lifecycle-livedata-ktx বা kotlinx-coroutines-core (StateFlow)। ইনজেকশনের জন্য — Hilt বা Koin। অ্যাসিনক্রোনাস অপারেশনের জন্য — Kotlin Coroutines। জটিল ডেটা বাইন্ডিংয়ের জন্য — DataBinding। Jetpack Compose-এ (2022 থেকে Google-এ সুপারিশকৃত), compose-runtime ও lifecycle-viewmodel-compose যথেষ্ট।
SwiftUI (2019) রিঅ্যাকটিভ আর্কিটেকচারের জন্য ডিজাইন করা: @State ও @Published ডেটা পরিবর্তনে স্বয়ংক্রিয়ভাবে View পুনরায় রেন্ডার করে। MVVM SwiftUI-র জন্য প্রাকৃতিক ফিট: View — @ViewBuilder, ViewModel — ObservableObject। Apple MVVM-কে একমাত্র প্যাটার্ন হিসেবে চাপিয়ে দেয় না, কিন্তু 2019 থেকে সব প্রশিক্ষণ উপকরণ ViewModel + SwiftUI ব্যবহার করে। UIKit-এর জন্য, Apple MVC বা Coordinator সুপারিশ করে।
Android: viewModelScope ViewModel পরিষ্কারের সময় স্বয়ংক্রিয়ভাবে coroutines বাতিল করে। iOS: Combine-এর AnyCancellable ধারক অবজেক্ট ডি-অ্যালোকেট হলে স্বয়ংক্রিয়ভাবে আনসাবস্ক্রাইব করে। SwiftUI @StateObject স্বয়ংক্রিয়ভাবে লাইফসাইকেল পরিচালনা করে। মূল নিয়ম: ViewModel-এ View/Context-এর রেফারেন্স রাখবেন না, পরিষ্কারের সময় দীর্ঘ-চালু অপারেশন বাতিল করুন, ক্লোজারে weak self ব্যবহার করুন।
StateFlow আধুনিক পছন্দ। LiveData সহজ ও লাইফসাইকেল-নিরাপদ, কিন্তু StateFlow বেশি শক্তিশালী: coroutines-এর সাথে কাজ করে, flatMap, combine, filter সমর্থন করে, @Nullable অ্যানোটেশন প্রয়োজন নেই। একমাত্র পরিস্থিতি যেখানে LiveData পছন্দনীয় — Java কোডের সাথে কাজ যেখানে StateFlow (Kotlin Flow API) উপলব্ধ নয়। Google নতুন Kotlin প্রজেক্টের জন্য StateFlow সুপারিশ করে।
সারসংক্ষেপ
আমরা একটি মোবাইল অ্যাপ্লিকেশন টার্নকি তৈরি করব
IT Sectr 2017 সাল থেকে স্টার্টআপ এবং ব্যবসার জন্য iOS এবং Android অ্যাপ্লিকেশন তৈরি করে। আমরা আপনাকে পরামর্শ দেব এবং সেরা সমাধান প্রস্তাব করব।
আরও পড়ুন