مقایسه دو الگوی معماری MVVM و MVI در اندروید

مقدمه

در توسعه اپلیکیشن‌های اندروید، انتخاب معماری مناسب برای مدیریت وضعیت برنامه و تعاملات کاربر از اهمیت ویژه‌ای برخوردار است. این مقاله به بررسی دو معماری پرکاربرد، یعنی MVVM (Model-View-ViewModel) و MVI (Model-View-Intent)، می‌پردازد و پیاده‌سازی آن‌ها را با استفاده از Flow در Jetpack Compose تحلیل می‌کند.

معرفی معماری MVVM

در معماری MVVM، ViewModel به‌عنوان واسطه‌ای بین Model و View عمل می‌کند. این مؤلفه وظیفه مدیریت داده‌ها را بر عهده دارد و آن‌ها را از طریق ابزارهایی مانند LiveData یا Flow به رابط کاربری (UI) منتقل می‌کند.

پیاده‌سازی ViewModelA (MVVM)

class ViewModelA : ViewModel() {
    private val _email = MutableStateFlow(&quot&quot)
    val email = _email.asStateFlow()

    private val _password = MutableStateFlow(&quot&quot)
    val password = _password.asStateFlow()

    fun updateEmail(email: String) {
        _email.update { email }
    }

    fun updatePassword(password: String) {
        _password.update { password }
    }

    val isEmailValid = email
        .map { it.isValidEmail() }
        .stateIn(viewModelScope,
 SharingStarted.WhileSubscribed(5000),
 false)

    val isPasswordValid = password
        .map { it.isValidPassword() }
        .stateIn(viewModelScope,
 SharingStarted.WhileSubscribed(5000),
 false)

    val canRegister = combine(isEmailValid, isPasswordValid) { emailValid, passwordValid ->
        emailValid && passwordValid
    }.stateIn(viewModelScope, 
SharingStarted.WhileSubscribed(5000),
 false)
}

توضیح کلی معماری MVVM در این پیاده‌سازی:

  • مدیریت داده‌ها: ViewModelA داده‌های مربوط به ایمیل و رمز عبور را ذخیره و مدیریت می‌کند.
  • اعتبارسنجی: از Flow برای اعتبارسنجی ورودی‌ها به‌صورت واکنشی استفاده شده است.
  • منطق ثبت‌نام: قابلیت ثبت‌نام با ترکیب نتایج اعتبارسنجی ایمیل و رمز عبور (isEmailValid و isPasswordValid) تعیین می‌شود.

توضیح جزئی استفاده از Flow:

  • از MutableStateFlow: برای ذخیره و ارائه مقادیر به UI استفاده می‌شود.
  • از map: برای تبدیل و اعتبارسنجی داده‌های ورودی به کار رفته است.
  • از combine: دو جریان داده (اعتبار ایمیل و رمز عبور) را ترکیب می‌کند.
  • از stateIn: برای نگه‌داری مقدار نهایی در طول چرخه حیات ViewModel استفاده شده است.

معرفی معماری MVI

معماری MVI بر پایه یک وضعیت (State) واحد بنا شده است که تمامی اطلاعات مربوط به View را در خود نگه می‌دارد. این معماری از اصل جریان یک‌طرفه داده‌ها پیروی می‌کند و تغییرات را به‌صورت متمرکز مدیریت می‌کند.

پیاده‌سازی ViewModelB (MVI)

data class RegisterState(
    val email: String = &quot&quot,
    val password: String = &quot&quot,
    val isEmailValid: Boolean = false,
    val isPasswordValid: Boolean = false,
    val canRegister: Boolean = false
)

class ViewModelB : ViewModel() {
    private val _state = MutableStateFlow(RegisterState())
    val state = _state.asStateFlow()

    fun updateEmail(email: String) {
        _state.update { it.copy(email = email) }
    }

    fun updatePassword(password: String) {
        _state.update { it.copy(password = password) }
    }

    init {
        state
            .distinctUntilChangedBy { it.email }
            .map { it.email.isValidEmail() }
            .onEach { isEmailValid ->
                _state.update { it.copy(isEmailValid = isEmailValid) }
            }
            .launchIn(viewModelScope)

        state
            .distinctUntilChangedBy { it.password }
            .map { it.password.isValidPassword() }
            .onEach { isPasswordValid ->
                _state.update { it.copy(isPasswordValid = isPasswordValid) }
            }
            .launchIn(viewModelScope)

        state
            .onEach { currentState ->
                _state.update {
                    it.copy(canRegister = currentState.isEmailValid && currentState.isPasswordValid)
                }
            }
            .launchIn(viewModelScope)
    }
}

توضیح کلی معماری MVI در این پیاده‌سازی:

  • وضعیت واحد: یک کلاس داده به نام RegisterState تمامی اطلاعات صفحه را در خود نگه می‌دارد.
  • به‌روزرسانی وضعیت: تغییرات ورودی‌ها (ایمیل و رمز عبور) به‌صورت متمرکز در State اعمال می‌شود و اعتبارسنجی‌ها بر اساس آن انجام می‌گیرد.
  • مدیریت متمرکز: وضعیت کلی صفحه با توجه به تغییرات ورودی‌ها به‌روز می‌شود.

توضیح جزئی استفاده از Flow:

  • ازdistinctUntilChangedBy: برای جلوگیری از پردازش‌های اضافی هنگام عدم تغییر مقدار استفاده شده است و از پردازش غیرضروری در صورت عدم تغییر مقادیر جلوگیری می‌کند.
  • ازmap: برای بررسی اعتبار ایمیل و رمز عبور استفاده شده است.
  • از onEach: برای به‌روزرسانی مقدار state پس از پردازش مقدار ورودی به کار رفته است.
  • از (viewModelScope)launchIn: برای اجرای فرآیندها در viewModelScope استفاده شده است.

مقایسه MVVM و MVI

مقایسه کلی
مقایسه کلی

پیاده‌سازی UI با Jetpack Compose

مثال UI برای MVVM

@Composable
fun RegisterScreenA(
    viewModel: ViewModelA,
    modifier: Modifier = Modifier
) {
    val email by viewModel.email.collectAsState()
    val password by viewModel.password.collectAsState()
    val canRegister by viewModel.canRegister.collectAsState()
    // پیاده‌سازی رابط کاربری بر اساس این مقادیر
}

مثال UI برای MVI

@Composable
fun RegisterScreenB(
    registerState: RegisterState,
    modifier: Modifier = Modifier
) {
    // استفاده از registerState به جای viewModel
}

نتیجه‌گیری

در این مقاله، دو معماری MVVM و MVI بررسی شدند. معماری MVVM ساده‌تر است و برای پروژه‌های کوچک و متوسط مناسب می‌باشد. از سوی دیگر، معماری MVI برای پروژه‌های بزرگ که مدیریت وضعیت پیچیده‌تری دارند، انتخاب بهتری محسوب می‌شود. استفاده از Flow در هر دو معماری امکان مدیریت داده‌ها به‌صورت واکنشی را فراهم می‌کند.

کد به صورت کامل در گیت هاب موجود(لینک زیر) است در صورتی که دوست داشتید امتیاز بدید متشکرم

I think using use-case with mvi in this approach is better (model A vs model B) #android #kotlin #mvvm #mvi