MVI (Model-View-Intent) एक रिएक्टिव आर्किटेक्चरल पैटर्न है जो यूनिडायरेक्शनल डेटा फ्लो और अपरिवर्तनीय स्थिति पर आधारित है। MVVM के विपरीत, जहाँ ViewModel में कई StateFlows हो सकते हैं, MVI एक एकल स्थिति (State), अपरिवर्तनीय इरादे (Intent) और एक शुद्ध रिड्यूसर फ़ंक्शन (Reducer) परिभाषित करता है। MVI किसी भी समय स्क्रीन स्थिति की पूर्वानुमेयता की गारंटी देता है। इस पैटर्न को Android समुदाय में Mosby और Orbit लाइब्रेरीज़ द्वारा लोकप्रिय बनाया गया। अधिक जानकारी 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 classes, 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 एक क्लोज़र के माध्यम से 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 में। स्थिति को वेरिएंट के साथ sealed class/interface के रूप में परिभाषित किया गया है: Loading, Success(data), Error(message)। यह गारंटी देता है कि View असंगत स्थिति में समाप्त नहीं होगी — जब loading=true हो तो डेटा प्रदर्शित नहीं किया जा सकता क्योंकि Loading और Success अलग-अलग वर्ग हैं। स्थिति से संबंधित सभी डेटा sealed वेरिएंट के अंदर रहता है: 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) के माध्यम से iOS के लिए बहुत अच्छा काम करता है — SwiftUI समुदाय की सबसे लोकप्रिय आर्किटेक्चर। 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 एप्लिकेशन बनाता है। हम आपको सलाह देंगे और सर्वोत्तम समाधान प्रस्तावित करेंगे।
यह भी पढ़ें