آموزش معماری MVI در اندروید به صورت گام به گام
الگوهای معماری در اندروید روز به روز در حال پیشرفت هستند. همانطور که ما برنامه ها را توسعه می دهیم ، با چالش ها و مشکلات جدیدی روبرو می شویم. با ادامه حل چالش های مشابه، الگوهای جدیدی کشف خواهند شد. به عنوان توسعه دهندگان اندروید ، MVC ، MVP و MVVM را به عنوان متداول ترین الگوهای مورد استفاده داریم.
همه آنها از یک رویکرد برنامه نویسی ضروری استفاده می کنند. با این رویکرد، حتی اگر بیشتر چالشهای ما حل شود ، ما همچنان با چالشهایی در رابطه با ایمنی thread ها ، حفظ حالتهای برنامه مواجه هستیم . با این کار، بیایید ببینیم الگوی معماری MVI چیست، چگونه این چالش ها را حل می کند، و چگونه می توان با MVI شروع به کار کرد.
با بزرگتر شدن پروژههای اندرویدی، مدیریت State و جریان دادهها به یکی از مهمترین چالشهای توسعه تبدیل میشود. معماریهای مختلفی مانند:
- MVC
- MVP
- MVVM
برای حل این مشکلات معرفی شدند، اما در اپلیکیشنهای مدرن، مخصوصاً پروژههای مبتنی بر Reactive Programming و Jetpack Compose، معماری MVI محبوبیت زیادی پیدا کرده است.
MVI مخفف:
Model - View - Intent
است.
این معماری تمرکز ویژهای روی:
- مدیریت State
- جریان داده یکطرفه
- کاهش Bug
- Predictable UI
- Reactive UI
دارد.
بیایید ببینیم که نقش هر یک از اجزای MVI چیست.
- Model : برخلاف سایر الگوها، در مدل MVI وضعیت رابط کاربری را نشان می دهد. به عنوان مثال، UI ممکن است حالت های مختلفی مانند بارگیری داده، تغییر در رابط کاربری با اقدامات کاربر، خطاها، وضعیت های موقعیت فعلی صفحه نمایش کاربر داشته باشد. هر حالت شبیه به شی در مدل ذخیره می شود.
- View : View در MVI رابط ما است که می تواند در Activities و Fragment ها پیاده سازی شود. این به معنای داشتن یک ظرف است که می تواند حالت های مختلف مدل را بپذیرد و آن را به عنوان یک رابط کاربری نمایش دهد. آنها از مقاصد قابل مشاهده استفاده می کنند برای پاسخ به اقدامات کاربر.
- Intent : این Intent آنطور که اندروید قبلاً معرفی کرده بود نیست. نتیجه اقدامات کاربر به عنوان یک مقدار ورودی به Intent ارسال می شود. به نوبه خود، می توانیم بگوییم که مدل هایی را به عنوان ورودی به Intent ها ارسال خواهیم کرد که می توانند آن را از طریق Views بارگیری کنند.

MVI چیست؟
در معماری MVI تمام تغییرات رابط کاربری از طریق یک مسیر مشخص انجام میشوند.
کاربر یک Intent ارسال میکند، سپس سیستم آن را پردازش کرده و در نهایت یک State جدید تولید میشود.
ساختار کلی:
User Action
↓
Intent
↓
ViewModel
↓
New State
↓
UI Update

تفاوت MVI با MVVM
معماری MVVM
در MVVM معمولاً:
- ViewModel چندین LiveData یا StateFlow دارد
- State ممکن است پراکنده باشد
- مدیریت وضعیت پیچیدهتر میشود
معماری MVI
در MVI:
- فقط یک State اصلی وجود دارد
- تمام تغییرات از طریق Intent انجام میشود
- Data Flow یکطرفه است
- Debug و Testing راحتتر است
ساختار پروژه
برای پروژه ، ما یک نسخه ساده از MVI را پیاده سازی می کنیم و پکیج های ما در پروژه به شکل زیر خواهد بود.

مزایای معماری MVI
1. مدیریت بهتر State
تمام وضعیت صفحه داخل یک State نگهداری میشود.
2. جریان داده قابل پیشبینی
هیچ تغییر مستقیمی روی UI وجود ندارد.
3. مناسب Jetpack Compose
معماری MVI با Compose هماهنگی فوقالعادهای دارد.
4. تستپذیری بالا
بهدلیل Stateless بودن UI، تست بسیار سادهتر میشود.
معایب MVI
1. Boilerplate بیشتر
نیاز به تعریف:
- Intent
- State
- Reducer
دارد.
2. پیچیدگی اولیه
برای پروژههای کوچک ممکن است بیشازحد پیچیده باشد.
اجزای اصلی MVI
معماری MVI معمولاً از بخشهای زیر تشکیل میشود:
Intent
State
ViewModel
Reducer
View
Repository
مفهوم Intent
Intent نشاندهنده عملی است که کاربر انجام میدهد.
مثالها:
- کلیک روی دکمه
- جستجو
- Refresh
- ارسال فرم
تعریف Intent
sealed class UserIntent {
data object LoadUsers : UserIntent()
data class Search(
val query: String
) : UserIntent()
}
مفهوم State
State نمایانگر وضعیت کامل UI است.
تعریف State
data class UserState(
val isLoading: Boolean = false,
val users: List<String> = emptyList(),
val error: String? = null
)
مفهوم Reducer
Reducer وظیفه تولید State جدید را دارد.
ساخت پروژه نمونه
در این آموزش یک صفحه ساده میسازیم که:
- کاربران را دریافت میکند
- Loading نمایش میدهد
- خطا را مدیریت میکند
Dependency های موردنیاز
build.gradle
dependencies {
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.8.0"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.1"
implementation "androidx.compose.ui:ui"
implementation "androidx.compose.material3:material3"
}
ساخت Intent
sealed class UserIntent {
data object LoadUsers : UserIntent()
data class SearchUser(
val query: String
) : UserIntent()
}
ساخت State
data class UserState(
val loading: Boolean = false,
val users: List<String> = emptyList(),
val error: String? = null
)
ساخت Repository
class UserRepository {
suspend fun getUsers(): List<String> {
delay(2000)
return listOf(
"Ali",
"Sara",
"Maryam",
"Reza"
)
}
}
ساخت ViewModel در MVI
class UserViewModel(
private val repository: UserRepository
) : ViewModel() {
private val _state =
MutableStateFlow(UserState())
val state: StateFlow<UserState> =
_state
fun onIntent(intent: UserIntent) {
when(intent) {
is UserIntent.LoadUsers -> {
loadUsers()
}
is UserIntent.SearchUser -> {
}
}
}
private fun loadUsers() {
viewModelScope.launch {
_state.value =
_state.value.copy(
loading = true
)
try {
val users =
repository.getUsers()
_state.value =
_state.value.copy(
loading = false,
users = users
)
} catch (e: Exception) {
_state.value =
_state.value.copy(
loading = false,
error = e.message
)
}
}
}
}
توضیح جریان داده
مراحل اجرای برنامه:
User Click
↓
Intent ارسال میشود
↓
ViewModel پردازش میکند
↓
Repository داده را دریافت میکند
↓
State جدید ساخته میشود
↓
UI آپدیت میشود
اتصال UI به ViewModel
صفحه Compose
@Composable
fun UserScreen(
viewModel: UserViewModel
) {
val state by
viewModel.state.collectAsState()
LaunchedEffect(Unit) {
viewModel.onIntent(
UserIntent.LoadUsers
)
}
Box(
modifier = Modifier.fillMaxSize()
) {
when {
state.loading -> {
CircularProgressIndicator()
}
state.error != null -> {
Text(
text = state.error!!
)
}
else -> {
LazyColumn {
items(state.users) {
Text(text = it)
}
}
}
}
}
}
چرا State واحد مهم است؟
در MVI کل وضعیت صفحه داخل یک State ذخیره میشود.
مثال:
UserState(
loading = false,
users = list,
error = null
)
این موضوع باعث میشود:
- وضعیت UI قابل پیشبینی باشد
- Debug راحتتر شود
- Race Condition کمتر شود
مدیریت Event ها در MVI
گاهی لازم است Event هایی مثل:
- Toast
- Navigation
- Snackbar
مدیریت شوند.
برای این کار معمولاً از SharedFlow استفاده میشود.
نمونه Event
sealed class UserEvent {
data class ShowToast(
val message: String
) : UserEvent()
}
SharedFlow در ViewModel
private val _event =
MutableSharedFlow<UserEvent>()
val event = _event.asSharedFlow()
ارسال Event
_event.emit(
UserEvent.ShowToast(
"Users Loaded"
)
)
دریافت Event در Compose
LaunchedEffect(Unit) {
viewModel.event.collect {
when(it) {
is UserEvent.ShowToast -> {
}
}
}
}
استفاده از Reducer حرفهای
در پروژههای بزرگ بهتر است Reducer جداگانه داشته باشید.
نمونه Reducer
fun reduce(
oldState: UserState,
result: Result
): UserState {
return when(result) {
is Result.Loading -> {
oldState.copy(
loading = true
)
}
is Result.Success -> {
oldState.copy(
loading = false,
users = result.users
)
}
is Result.Error -> {
oldState.copy(
loading = false,
error = result.message
)
}
}
}
بهترین ساختار پروژه MVI
presentation/
intent/
state/
event/
reducer/
viewmodel/
domain/
data/
repository/
network/
تفاوت Intent و Event
Intent
از UI به ViewModel ارسال میشود.
مثال:
Button Click
Search
Refresh
Event
از ViewModel به UI ارسال میشود.
مثال:
Toast
Snackbar
Navigation
استفاده از StateFlow در MVI
در پروژههای مدرن بهتر است از:
StateFlow
استفاده کنید.
زیرا:
- Reactive است
- با Compose هماهنگ است
- Lifecycle Aware است
چرا MVI برای Compose عالی است؟
Jetpack Compose بر اساس State کار میکند.
در MVI نیز:
State → UI
بنابراین هماهنگی بسیار بالایی وجود دارد.
مدیریت Loading در MVI
_state.value =
_state.value.copy(
loading = true
)
مدیریت خطا
_state.value =
_state.value.copy(
error = e.message
)
تستپذیری MVI
در MVI تست بسیار راحتتر است.
مثال:
assertEquals(
true,
state.loading
)
استفاده از Clean Architecture همراه MVI
ترکیب:
MVI + Clean Architecture
یکی از بهترین معماریهای مدرن اندروید است.
تفاوت MVI و Redux
معماری MVI الهام گرفته از Redux است.
شباهتها:
- Single State
- Reducer
- Immutable State
Immutable State چیست؟
در MVI نباید State را مستقیم تغییر دهید.
اشتباه:
state.loading = true
صحیح:
state.copy(
loading = true
)
بهترین روشهای پیادهسازی MVI
- State کوچک نگه دارید
- Event و State را جدا کنید
- Reducer مستقل داشته باشید
- از Immutable Data استفاده کنید
- منطق Business را داخل Repository قرار دهید
اشتباهات رایج در MVI
- قرار دادن Event داخل State
- داشتن چند State مستقل
- تغییر مستقیم State
- ارسال Intent از Repository
مثال کامل چرخه MVI
User Click Search
↓
SearchIntent
↓
ViewModel
↓
Repository API Call
↓
Reducer
↓
New State
↓
Compose Recompose
چه زمانی از MVI استفاده کنیم؟
MVI مناسب است برای:
- پروژههای بزرگ
- اپلیکیشنهای Compose
- سیستمهای Reactive
- پروژههای چند State
- اپلیکیشنهای RealTime
چه زمانی مناسب نیست؟
برای:
- پروژههای بسیار کوچک
- صفحات ساده
- Prototype ها
ممکن است MVVM سادهتر باشد.
کتابخانههای محبوب MVI
- Orbit MVI
- Mavericks
- Mobius
- MVIKotlin
نمونه Orbit MVI
class UserViewModel : ContainerHost<
UserState,
UserEvent
>, ViewModel() {
}
جمعبندی
در این مقاله با معماری MVI در اندروید آشنا شدیم.
همچنین یاد گرفتیم:
- Intent چیست
- State چیست
- Reducer چگونه کار میکند
- جریان داده در MVI
- اتصال MVI به Compose
- مدیریت Event
- استفاده از StateFlow
- بهترین روشهای معماری
معماری MVI یکی از قدرتمندترین معماریهای مدرن اندروید است و مخصوصاً در پروژههای مبتنی بر Jetpack Compose عملکرد فوقالعادهای دارد.