MVI (Model-View-Intent) ایک ری ایکٹیو آرکیٹیکچرل پیٹرن ہے جو یک سمتی ڈیٹا فلو اور ناقابل تغیر حالت پر مبنی ہے۔ MVVM کے برعکس، جہاں ایک ViewModel کے متعدد StateFlows ہو سکتے ہیں، MVI ایک واحد حالت (State)، ناقابل تغیر ارادے (Intent) اور ایک خالص ریڈیوسر فنکشن (Reducer) کی وضاحت کرتا ہے۔ MVI کسی بھی وقت اسکرین کی حالت کی پیش گوئی کی ضمانت دیتا ہے۔ یہ پیٹرن Mosby اور Orbit لائبریریوں کے ذریعے Android کمیونٹی میں مقبول ہوا۔ مزید معلومات Arkadii Ivanov کے MVIKotlin میں۔
اہم نکات
MVI (Model-View-Intent) ایک ری ایکٹیو آرکیٹیکچرل پیٹرن ہے جو Redux اور Cycle.js کے اصولوں پر بنایا گیا ہے۔ Model ناقابل تغیر اسکرین حالت ہے، Intent صارف یا نظام کا ارادہ ہے، View حالت کو سبسکرائب کرتا ہے اور Intents بھیجتا ہے۔ ڈیٹا ایک سائیکل میں بہتا ہے: صارف View کے ساتھ تعامل کرتا ہے → View ایک Intent بناتا ہے → Intent Reducer کے ذریعے پروسیس ہوتا ہے → Reducer ایک نئی حالت بناتا ہے → View نئی حالت وصول کرتا ہے اور دوبارہ رینڈر کرتا ہے۔
MVI اور MVVM کے درمیان بنیادی فرق واحد سچائی کا ذریعہ (Single Source of Truth) ہے۔ MVVM میں، ایک ViewModel کے متعدد LiveData/StateFlow (userState، loadingState، errorState) ہو سکتے ہیں، جو عدم مطابقت کا باعث بنتا ہے: loading=true اور user=null ایک ساتھ۔ MVI میں، بالکل ایک sealed class/interface State ہے جو پوری اسکرین کی حالت بیان کرتا ہے۔ کسی بھی وقت، اسکرین کی حالت منفرد طور پر متعین ہوتی ہے — ڈیٹا پہلے سے لوڈ ہونے پر loading=true ہونا ناممکن ہے۔ IT Sectr میں، ہم پیچیدہ منطق والی اسکرینوں — آرڈر فارم، ملٹی اسٹیپ رجسٹریشن، مالیاتی اسکرینوں — کے لیے MVI استعمال کرتے ہیں جہاں حالت کی پیش گوئی اہم ہے۔
| جزو | MVI میں کردار | مثال |
|---|---|---|
| Intent | صارف یا نظام کا ارادہ | LoadUser, Refresh, SubmitForm |
| State | ناقابل تغیر اسکرین حالت | sealed class UserState |
| Reducer | خالص فنکشن: State + Intent → State | fun reduce(state, intent) -> state |
| Middleware | ضمنی اثرات کا انتظام | نیٹ ورک کی درخواست، DB تحریر |
MVI سائیکل پانچ مراحل پر مشتمل ہے: 1) View ایک Intent بھیجتا ہے (مثلاً LoadUser(42))؛ 2) Middleware (EffectHandler) ایک ضمنی اثر انجام دیتا ہے — ایک نیٹ ورک کی درخواست؛ 3) نتیجہ نظام میں ایک نئے Intent کے طور پر واپس آتا ہے؛ 4) Reducer موجودہ حالت اور Intent لیتا ہے، ایک نئی حالت بناتا ہے؛ 5) View نئی حالت وصول کرتا ہے اور دوبارہ رینڈر کرتا ہے۔ ہر مرحلہ پیش گوئی کے قابل اور الگ تھلگ جانچنے کے قابل ہے۔
Android میں MVI Intent اور State کے لیے sealed کلاسز، MVI منطق کے ساتھ ViewModel، اور ری ایکٹیو رینڈرنگ کے لیے Jetpack Compose کا استعمال کرتے ہوئے نافذ کیا جاتا ہے۔ ViewModel View سے Intent وصول کرتا ہے، ضمنی اثرات Middleware کو سونپتا ہے، Reducer چلاتا ہے، اور StateFlow کے ذریعے نئی حالت شائع کرتا ہے۔ Jetpack Compose حالت بدلنے پر UI کو دوبارہ رینڈر کرتا ہے — MVI سائیکل کے لیے مثالی۔
// Intent — صارف کے ارادے
sealed interface UserIntent {
data class LoadUser(val userId: Int) : UserIntent
data object Refresh : UserIntent
}
// State — واحد اسکرین حالت
sealed interface UserState {
data object Idle : UserState
data object Loading : UserState
data class Success(val user: User) : UserState
data class Error(val message: String) : UserState
}
// Reducer — خالص فنکشن
object UserReducer {
fun reduce(state: UserState, intent: UserIntent): UserState = when (intent) {
is UserIntent.LoadUser -> UserState.Loading
is UserIntent.Refresh -> UserState.Loading
}
}
// MVI کے ساتھ ViewModel
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _state = MutableStateFlow<UserState>(UserState.Idle)
val state: StateFlow<UserState> = _state.asStateFlow()
fun process(intent: UserIntent) {
val newState = UserReducer.reduce(_state.value, intent)
_state.value = newState
when (intent) {
is UserIntent.LoadUser -> loadUser(intent.userId)
is UserIntent.Refresh -> loadUser(/* پچھلا ID */)
}
}
private fun loadUser(userId: Int) {
viewModelScope.launch {
repository.getUser(userId)
.onSuccess { user ->
_state.value = UserState.Success(user)
}
.onFailure { e ->
_state.value = UserState.Error(e.message ?: "Error")
}
}
}
}
// View Intent بھیجتا ہے
fun UserScreen(viewModel: UserViewModel) {
val state by viewModel.state.collectAsState()
LaunchedEffect(Unit) { viewModel.process(UserIntent.LoadUser(42)) }
when (state) {
is UserState.Loading -> CircularProgressIndicator()
is UserState.Success -> UserCard((state as UserState.Success).user)
is UserState.Error -> ErrorView((state as UserState.Error).message)
is UserState.Idle -> Text("Press to load")
}
}
Middleware اور ضمنی اثرات — MVI میں، ایک خالص Reducer نیٹ ورک کی درخواستیں نہیں کر سکتا۔ Middleware (جسے EffectHandler یا Bootstrapper بھی کہا جاتا ہے) Intent کو پروسیس کرتا ہے، ضمنی اثر انجام دیتا ہے، اور سائیکل میں واپس ایک نیا Intent بھیجتا ہے۔ Orbit MVI اور MVIKotlin لائبریریاں قابل جانچ اثرات کے ساتھ بلٹ ان Middleware سپورٹ فراہم کرتی ہیں۔ Middleware کے بغیر، MVI اضافی Intent اور State ساخت کے ساتھ MVVM میں تبدیل ہو جاتا ہے۔
Arkadii Ivanov کا MVIKotlin Kotlin Multiplatform کے لیے سب سے مقبول MVI لائبریری ہے۔ یہ Android، iOS، ویب اور JVM کو سپورٹ کرتی ہے۔ یہ اجزاء فراہم کرتی ہے: Store (ViewModel)، Bootstrapper (ابتدائی اثرات)، Reducer، Middleware۔ اکتوبر 2025 تک، لائبریری نے GitHub پر 2.5K ستارے جمع کیے ہیں اور تجارتی منصوبوں میں استعمال ہو رہی ہے، جس میں بڑے روسی بینکوں کی ایپلی کیشنز شامل ہیں۔ IT Sectr میں، ہم مشترکہ کاروباری منطق کے ساتھ کراس پلیٹ فارم KMP پروجیکٹس کے لیے MVIKotlin استعمال کرتے ہیں۔
iOS میں MVI Combine-ViewModel کے بغیر، Intent → State سائیکل کے ذریعے نافذ کیا جاتا ہے۔ View ایک closure کے ذریعے Intent بھیجتا ہے، Reducer ایک خالص فنکشن ہے، اور State ناقابل تغیر فیلڈز کے ساتھ ایک struct ہے۔ جب State بدلتا ہے تو SwiftUI View کو دوبارہ رینڈر کرتا ہے، اضافی @Published خصوصیات کے بغیر MVI سائیکل میں بالکل فٹ بیٹھتا ہے۔ iOS پر MVI خاص طور پر ان SwiftUI ڈویلپرز میں مقبول ہے جو Redux (JavaScript) سے منتقل ہوئے ہیں۔
// State — ناقابل تغیر ڈھانچہ
struct UserState: Equatable {
var user: User?
var isLoading = false
var errorMessage: String?
}
// Intent — ارادوں کے ساتھ enum
enum UserIntent {
case loadUser(id: Int)
case refresh
case userLoaded(User)
case loadFailed(Error)
}
// Reducer — خالص فنکشن
func userReducer(state: UserState, intent: UserIntent) -> UserState {
var newState = state
switch intent {
case .loadUser, .refresh:
newState.isLoading = true
newState.errorMessage = nil
case .userLoaded(let user):
newState.isLoading = false
newState.user = user
case .loadFailed(let error):
newState.isLoading = false
newState.errorMessage = error.localizedDescription
}
return newState
}
// Store — حالت کا مالک ہے اور اثرات کا انتظام کرتا ہے
final class UserStore: ObservableObject {
@Published private(set) var state = UserState()
private let service: UserService
init(service: UserService) {
self.service = service
}
func dispatch(_ intent: UserIntent) {
// 1. Reducer حالت کو اپ ڈیٹ کرتا ہے
state = userReducer(state: state, intent: intent)
// 2. Side effects (اگر ضرورت ہو)
switch intent {
case .loadUser(let id), .refresh:
service.fetchUser(id: id) { [weak self] result in
switch result {
case .success(let user):
self?.dispatch(.userLoaded(user))
case .failure(let error):
self?.dispatch(.loadFailed(error))
}
}
default: break
}
}
}
TCA (The Composable Architecture) Point-Free کی طرف سے iOS کے لیے سب سے مقبول MVI نفاذ ہے، جو SwiftUI اور Combine پر بنایا گیا ہے۔ TCA Store، Reducer، Effect اور Environment فراہم کرتا ہے۔ اکتوبر 2025 تک، GitHub پر اس کے ستارے 13K سے تجاوز کر گئے ہیں — iOS پر MVI کا حقیقی معیار ہے۔ TCA Starbucks ایپس، Airbnb (جزوی طور پر) اور بہت سے انڈی پروجیکٹس میں استعمال ہوتا ہے۔ کسٹم MVI کے برعکس، TCA جانچ، نیویگیشن اور ضمنی اثرات کو فوری طور پر حل کرتا ہے۔
iOS پر MVI بمقابلہ MVVM — TCA/MVI حالت کی پیش گوئی فراہم کرتا ہے لیکن زیادہ بائلر پلیٹ کوڈ (Reducer, State, Action) کی ضرورت ہوتی ہے۔ @Published کے ساتھ MVVM بنیادی اسکرینوں کے لیے آسان ہے۔ IT Sectr میں، ہم 80% اسکرینوں کے لیے MVVM اور 20% پیچیدہ اسکرینوں — مالی لین دین، ملٹی اسٹیپ فارم، ڈریگ اینڈ ڈراپ انٹرفیس — کے لیے MVI (TCA) استعمال کرتے ہیں، جہاں حالت کی غلطی صارف کو پیسے کھا سکتی ہے۔
MVI اور MVVM ایک ہی مسئلہ حل کرتے ہیں — پریزنٹیشن پرت کو منظم کرنا — لیکن حالت کے انتظام کے مختلف طریقوں کے ساتھ۔ MVVM متعدد ری ایکٹیو ذرائع (LiveData, @Published) کی اجازت دیتا ہے، جو عدم مطابقت کا باعث بن سکتا ہے۔ MVI کسی بھی وقت بالکل ایک حالت کی ضمانت دیتا ہے، اسے زیادہ سخت اور پیش گوئی کے قابل بناتا ہے، لیکن کوڈ کی مقدار بڑھاتا ہے۔
| معیار | MVVM | MVI |
|---|---|---|
| حالت | متعدد LiveData/StateFlow | واحد sealed class State |
| ڈیٹا کا بہاؤ | دو سمتی (View → ViewModel, LiveData → View) | یک سمتی (Intent → Reducer → State → View) |
| ضمنی اثرات | براہ راست ViewModel میں | Middleware/EffectHandler کے ذریعے |
| جانچ | ViewModel کے یونٹ ٹیسٹ | Reducer + Middleware کے یونٹ ٹیسٹ |
| بائلر پلیٹ کوڈ | کم سے کم | Reducer + State + Intent + Middleware |
MVI کب انتخاب کریں — وہ اسکرینیں جہاں حالت سختی سے متعین ہونی چاہیے: مالیاتی کارروائیاں، شاپنگ کارٹ، ہر مرحلے پر توثیق کے ساتھ ملٹی اسٹیپ فارم۔ ان منظرناموں میں، حالت کی غلطی کی لاگت (مثال کے طور پر، دو LiveData کے درمیان دوڑ کی حالت کی وجہ سے ایک آئٹم کے بغیر کارٹ کل دکھانا) اضافی کوڈ کی لاگت سے زیادہ ہے۔ MVVM میں آپ ٹیم کے نظم و ضبط پر بھروسہ کرتے ہیں؛ MVI میں آپ آرکیٹیکچر پر بھروسہ کرتے ہیں۔
MVVM کب کافی ہے — 80% معیاری اسکرینیں: صارف کی فہرست، پروفائل، ترتیبات، خبروں کی فیڈ۔ یہاں، ایک واحد حالت ضرورت سے زیادہ ہے، اور اضافی MVI ساخت ترقی کو سست کرے گی۔ IT Sectr میں، اصول یہ ہے: اگر کسی اسکرین میں منتقلی (لوڈنگ → ڈیٹا → خرابی → دوبارہ کوشش → لوڈنگ → ڈیٹا) کے ساتھ 3+ ممکنہ حالتیں ہیں — MVI استعمال کریں۔ اگر کسی اسکرین میں 1-2 غیر متزامن آپریشن ہیں — MVVM استعمال کریں۔
Sealed State — بہترین عمل MVI میں۔ حالت کو مختلف حالتوں (Loading، Success(data)، Error(message)) کے ساتھ sealed class/interface کے طور پر بیان کیا جاتا ہے۔ یہ ضمانت دیتا ہے کہ View متضاد حالت میں نہیں آئے گی — loading=true ہونے پر ڈیٹا ظاہر نہیں کیا جا سکتا کیونکہ Loading اور Success مختلف کلاسیں ہیں۔ حالت سے متعلق تمام ڈیٹا sealed variant کے اندر رہتا ہے: Success میں صارف ہوتا ہے، Error میں خرابی کا پیغام ہوتا ہے۔
Reducer کو خالص فنکشن رہنا چاہیے — API، DB یا SharedPreferences کالز کے بغیر۔ ایک خالص فنکشن State اور Intent لیتا ہے اور State لوٹاتا ہے۔ ضمنی اثرات (نیٹ ورک، DB، نیویگیشن، ٹوسٹ) Middleware میں یا Reducer کو کال کرنے کے بعد Store.dispatch میں سنبھالے جاتے ہیں۔ اگر Reducer ضمنی اثرات سے آلودہ ہو جاتا ہے، MVI جانچ کی اہلیت اور پیش گوئی کھو دیتا ہے — آپ کو بغیر فوائد کے اضافی ساخت کے ساتھ MVVM ملتا ہے۔
عام غلطیاں — sealed class کے بجائے nullable فیلڈز والی data class کے طور پر State کا اعلان: data class UserState(val user: User?, val isLoading: Boolean, val error: String?)۔ یہ MVVM کے برابر ہے، MVI نہیں — View کو درستگی کے لیے فیلڈ کے مجموعے چیک کرنے چاہئیں۔ sealed طریقہ میں، غلط مجموعے (isLoading=true اور user!=null) قسم کی سطح پر ناممکن ہیں۔ دوسری غلطی کاروباری منطق کو Middleware میں رکھنے کے بجائے Intent (Intent.LoadUserBeforeXHours) میں رکھنا ہے۔
اکثر پوچھے گئے سوالات
MVI ایک واحد ناقابل تغیر sealed State کلاس اور Reducer کے ذریعے یک سمتی ڈیٹا بہاؤ استعمال کرتا ہے۔ MVVM دو سمتی بائنڈنگ کے ساتھ متعدد LiveData/StateFlow کی اجازت دیتا ہے۔ MVI قسم کی سطح پر حالت کی مستقل مزاجی کی ضمانت دیتا ہے — loading=true اور user=null کا ایک ساتھ ہونا ناممکن ہے۔ MVVM ڈویلپر کے نظم و ضبط پر منحصر ہے۔
اہم: MVIKotlin (Arkadii Ivanov، 2.5K ستارے، Kotlin Multiplatform)، Orbit MVI (BabyJ، 1.3K ستارے)، Mobius (Spotify، Kotlin/Java)۔ MVIKotlin Kotlin کے لیے سب سے مقبول، Orbit سیکھنے میں سب سے آسان ہے۔ تینوں قابل جانچ Reducer اور Middleware کو سپورٹ کرتی ہیں۔ Jetpack Compose کے لیے، آپ sealed State + Reducer استعمال کرکے لائبریری کے بغیر سادہ MVI لکھ سکتے ہیں۔
نہیں — sealed Intent + sealed State + ViewModel + StateFlow انحصار کے بغیر کام کرنے والا MVI دیتے ہیں۔ لائبریریاں (MVIKotlin, Orbit, TCA) Middleware، ضمنی اثرات کی جانچ اور DI انضمام شامل کرتی ہیں۔ سادہ منصوبوں کے لیے، لائبریری کا وزن ناقابل جواز ہے۔ 20+ اسکرینوں والے پیچیدہ منصوبوں کے لیے، لائبریری منظم اثرات کے انتظام سے خود کو ادائیگی کرتی ہے۔
MVI TCA (The Composable Architecture) — SwiftUI کمیونٹی کا سب سے مقبول آرکیٹیکچر — کے ذریعے iOS کے لیے بہترین کام کرتا ہے۔ TCA بنیادی طور پر MVI + Redux + Combine ہے۔ iOS پر، آپ ObservableObject اور ایک خالص reducer فنکشن استعمال کرکے TCA کے بغیر MVI لاگو کر سکتے ہیں۔ ناقابل تغیر State کے ساتھ SwiftUI MVI سائیکل میں بالکل فٹ بیٹھتا ہے۔
Reducer کو خالص فنکشن کے طور پر یونٹ ٹیسٹ سے جانچا جاتا ہے: ابتدائی State سیٹ کریں، Intent بھیجیں، نتیجے میں آنے والے State کی جانچ کریں۔ Middleware کو ایک فرضی ذخیرہ سے جانچا جاتا ہے: تصدیق کریں کہ LoadUser کے بعد getUser کو کال کیا گیا تھا۔ ViewModel ٹیسٹ: Intent بھیجیں، StateFlow چیک کریں۔ MVI کا MVVM کے مقابلے میں جانچنا آسان ہے کیونکہ Reducer پوشیدہ انحصار کے بغیر ایک خالص فنکشن ہے۔
خلاصہ
ہم ایک موبائل ایپلیکیشن ٹرنکی تیار کریں گے
IT Sectr 2017 سے اسٹارٹ اپس اور کاروبار کے لیے iOS اور Android ایپلیکیشنز بناتا ہے۔ ہم آپ کو مشورہ دیں گے اور بہترین حل تجویز کریں گے۔
مزید پڑھیں