برنامه‌نویسی اندروید

آموزش معماری MVI در اندروید به صورت گام به گام

آموزش معماری MVI در اندروید

با افزایش پیچیدگی اپلیکیشن‌های اندرویدی، انتخاب یک معماری مناسب به یکی از مهم‌ترین تصمیم‌های هر توسعه‌دهنده تبدیل شده است. معماری صحیح نه‌تنها باعث خوانایی و نگهداری بهتر کد می‌شود، بلکه توسعه قابلیت‌های جدید، تست‌پذیری و مدیریت وضعیت (State Management) را نیز ساده‌تر می‌کند. به همین دلیل، الگوهای معماری اندروید در طول سال‌ها به‌طور مداوم تکامل یافته‌اند و هر نسل از آن‌ها تلاش کرده است محدودیت‌های نسل قبل را برطرف کند.

توسعه‌دهندگان اندروید معمولاً با معماری‌هایی مانند MVC، MVP و MVVM آشنا هستند. این الگوها سال‌ها به‌عنوان استاندارد توسعه اپلیکیشن‌های اندرویدی مورد استفاده قرار گرفته‌اند، اما در پروژه‌های بزرگ و پیچیده همچنان چالش‌هایی مانند مدیریت هم‌زمانی (Concurrency)، حفظ وضعیت برنامه (State)، مدیریت رویدادهای رابط کاربری و پیش‌بینی رفتار برنامه وجود دارد.

در همین راستا، معماری MVI (Model–View–Intent) به‌عنوان رویکردی مدرن و مبتنی بر جریان داده یک‌طرفه (Unidirectional Data Flow) معرفی شد. در MVI، تمامی تغییرات رابط کاربری از طریق Intentها انجام می‌شوند و وضعیت برنامه در قالب یک State واحد مدیریت می‌شود. این ویژگی باعث می‌شود رفتار برنامه قابل پیش‌بینی‌تر باشد، مدیریت State ساده‌تر انجام شود و احتمال بروز خطاهای ناشی از تغییرات هم‌زمان کاهش یابد.

در این مقاله به‌طور کامل بررسی خواهیم کرد که معماری MVI در اندروید چیست، چگونه کار می‌کند، چه تفاوتی با MVC، MVP و MVVM دارد، مزایا و معایب آن چیست و چگونه می‌توان از این معماری در پروژه‌های اندرویدی با استفاده از Kotlin و Jetpack Compose بهره برد.

معماری MVI

معماری MVI در اندروید

معماری MVI در اندروید چیست؟

MVI مخفف Model–View–Intent است و یکی از معماری‌های مدرن توسعه رابط کاربری در اندروید محسوب می‌شود. این معماری بر پایه مفهوم جریان داده یک‌طرفه (Unidirectional Data Flow) طراحی شده است؛ به این معنا که تمام تغییرات رابط کاربری از یک مسیر مشخص عبور می‌کنند و وضعیت (State) برنامه تنها از طریق رویدادهای تعریف‌شده به‌روزرسانی می‌شود.

در معماری MVI، هر تعامل کاربر مانند کلیک روی دکمه، وارد کردن متن یا ارسال فرم به‌عنوان یک Intent در نظر گرفته می‌شود. این Intent به لایه منطق برنامه ارسال شده و پس از پردازش، یک State جدید تولید می‌شود. در نهایت، رابط کاربری (View) بر اساس این State دوباره رندر شده و آخرین وضعیت برنامه را نمایش می‌دهد.

برخلاف معماری‌هایی مانند MVC یا MVVM که ممکن است وضعیت برنامه در چندین بخش مختلف تغییر کند، در MVI تنها یک منبع واحد برای نگهداری State وجود دارد. این ویژگی باعث می‌شود رفتار برنامه قابل پیش‌بینی‌تر باشد، اشکال‌زدایی ساده‌تر انجام شود و مدیریت وضعیت در پروژه‌های بزرگ آسان‌تر گردد.

به دلیل همین ساختار، معماری MVI در سال‌های اخیر محبوبیت زیادی در میان توسعه‌دهندگان اندروید، به‌ویژه در پروژه‌های مبتنی بر Jetpack Compose، پیدا کرده است.

ایجاد یک پروژه با معماری MVI

تاریخچه معماری MVI در اندروید

معماری MVI (Model–View–Intent) ریشه در برنامه‌نویسی تابعی (Functional Programming) و الگوی Unidirectional Data Flow دارد. برخلاف معماری‌هایی مانند MVC، MVP و MVVM که ابتدا برای توسعه نرم‌افزارهای سنتی طراحی شده بودند، MVI با هدف مدیریت بهتر وضعیت (State) و ساده‌تر کردن تعاملات رابط کاربری در برنامه‌های مدرن معرفی شد.

ایده اصلی MVI از معماری Model–View–Intent در فریم‌ورک Cycle.js و همچنین الگوی Elm Architecture الهام گرفته شده است. زبان برنامه‌نویسی Elm که برای توسعه رابط‌های کاربری واکنش‌گرا (Reactive UI) طراحی شده بود، مفهوم «یک منبع واحد برای نگهداری State» و «جریان داده یک‌طرفه» را به‌طور گسترده مطرح کرد. این رویکرد باعث می‌شد رفتار برنامه کاملاً قابل پیش‌بینی باشد و مدیریت تغییرات رابط کاربری ساده‌تر انجام شود.

با گسترش برنامه‌نویسی واکنش‌گرا (Reactive Programming) و محبوبیت کتابخانه‌هایی مانند RxJava، توسعه‌دهندگان اندروید نیز به استفاده از MVI علاقه‌مند شدند. در این معماری، تمامی تعاملات کاربر به‌صورت رویداد (Intent) دریافت می‌شوند، سپس منطق برنامه آن‌ها را پردازش کرده و یک وضعیت (State) جدید تولید می‌کند. در نهایت، رابط کاربری تنها بر اساس آخرین State نمایش داده می‌شود.

در سال‌های اخیر، با معرفی Jetpack Compose توسط گوگل، معماری MVI بیش از گذشته مورد توجه قرار گرفته است. از آنجا که Jetpack Compose نیز بر پایه مدیریت State و رابط کاربری واکنش‌گرا (Declarative UI) ساخته شده، بسیاری از مفاهیم MVI به‌صورت طبیعی با این فریم‌ورک هماهنگ هستند. به همین دلیل، امروزه بسیاری از پروژه‌های مدرن اندروید از MVI یا معماری‌های مشابه مبتنی بر جریان داده یک‌طرفه برای توسعه اپلیکیشن‌های مقیاس‌پذیر و قابل نگهداری استفاده می‌کنند.

اگرچه گوگل هیچ‌گاه MVI را به‌عنوان معماری رسمی اندروید معرفی نکرده است، اما بسیاری از توسعه‌دهندگان و کتابخانه‌های مطرح مانند Orbit MVI، MVIKotlin و Mavericks از این الگو استفاده می‌کنند و آن را گزینه‌ای مناسب برای پروژه‌های پیچیده و مبتنی بر State می‌دانند.

دیاگرام معماری MVI

دیاگرام معماری MVI

چرا معماری MVI در اندروید به وجود آمد؟

با بزرگ‌تر و پیچیده‌تر شدن اپلیکیشن‌های اندرویدی، معماری‌های قدیمی مانند MVC و MVP با چالش‌هایی مانند مدیریت دشوار وضعیت (State)، افزایش وابستگی بین بخش‌های مختلف برنامه و پیچیده شدن منطق رابط کاربری مواجه شدند. اگرچه معماری MVVM بسیاری از این مشکلات را برطرف کرد، اما در پروژه‌های بزرگ همچنان مدیریت تغییرات متعدد رابط کاربری و همگام نگه داشتن وضعیت برنامه می‌توانست دشوار باشد.

برای رفع این چالش‌ها، معماری MVI (Model–View–Intent) معرفی شد. ایده اصلی MVI این است که تمام تغییرات رابط کاربری از یک جریان داده یک‌طرفه (Unidirectional Data Flow) عبور کنند. در این معماری، هر تعامل کاربر به‌صورت یک Intent ثبت می‌شود، سپس وضعیت جدید برنامه (State) محاسبه شده و در نهایت رابط کاربری بر اساس همان وضعیت به‌روزرسانی می‌شود.

این رویکرد باعث می‌شود وضعیت برنامه همیشه قابل پیش‌بینی، قابل ردیابی و قابل مدیریت باشد. همچنین اشکال‌زدایی (Debugging)، تست‌نویسی و نگهداری پروژه‌های بزرگ آسان‌تر می‌شود؛ به همین دلیل MVI در سال‌های اخیر، به‌ویژه در توسعه اپلیکیشن‌های مدرن اندروید با Jetpack Compose، محبوبیت زیادی پیدا کرده است.

اجزای اصلی معماری MVI در اندروید

معماری MVI (Model–View–Intent) از سه جزء اصلی تشکیل شده است: Model، View و Intent. این سه بخش با همکاری یکدیگر، جریان یک‌طرفه داده (Unidirectional Data Flow) را ایجاد می‌کنند و باعث می‌شوند وضعیت برنامه همیشه قابل پیش‌بینی و مدیریت باشد.

در MVI، هر عملی که کاربر انجام می‌دهد ابتدا به‌عنوان یک Intent ثبت می‌شود. سپس این Intent توسط Model پردازش شده و وضعیت جدید (State) تولید می‌شود. در نهایت View بر اساس این وضعیت جدید، رابط کاربری را به‌روزرسانی می‌کند.

Model

Model مسئول مدیریت داده‌ها، منطق کسب‌وکار (Business Logic) و وضعیت (State) برنامه است. این بخش داده‌ها را از منابع مختلف مانند API، پایگاه داده یا Repository دریافت می‌کند، آن‌ها را پردازش کرده و نتیجه را به‌صورت یک State جدید در اختیار رابط کاربری قرار می‌دهد.

در معماری MVI، Model مستقیماً با View ارتباط برقرار نمی‌کند و تنها وضعیت جدید را منتشر می‌کند. این موضوع باعث کاهش وابستگی بین بخش‌های مختلف برنامه و افزایش قابلیت تست‌پذیری می‌شود.

وظایف Model:

  • مدیریت وضعیت (State) برنامه
  • اجرای منطق کسب‌وکار
  • دریافت و پردازش داده‌ها
  • ارتباط با Repository، API و پایگاه داده
  • تولید State جدید برای رابط کاربری

View

View همان رابط کاربری (UI) است که وضعیت فعلی برنامه را به کاربر نمایش می‌دهد. View هیچ منطق تجاری یا پردازش پیچیده‌ای انجام نمی‌دهد و تنها بر اساس آخرین State دریافتی، رابط کاربری را رندر می‌کند.

همچنین تمام تعاملات کاربر مانند کلیک روی دکمه، وارد کردن متن یا کشیدن صفحه را به‌صورت Intent به لایه بعدی ارسال می‌کند.

در اندروید، View می‌تواند یک Activity، Fragment یا یک صفحه ساخته‌شده با Jetpack Compose باشد.

وظایف View:

  • نمایش وضعیت فعلی برنامه
  • دریافت تعاملات کاربر
  • ارسال Intentها
  • به‌روزرسانی رابط کاربری بر اساس State

Intent

Intent بیانگر تمام اقداماتی است که کاربر در برنامه انجام می‌دهد. هر تعامل کاربر، مانند لمس یک دکمه، جستجو، ورود اطلاعات یا تازه‌سازی صفحه، به‌صورت یک Intent به سیستم ارسال می‌شود.

پس از دریافت Intent، منطق برنامه آن را پردازش کرده و در صورت نیاز وضعیت جدیدی ایجاد می‌کند. سپس View با دریافت State جدید، رابط کاربری را به‌روزرسانی می‌کند.

نمونه‌هایی از Intent عبارت‌اند از:

  • کلیک روی دکمه ورود
  • جستجوی یک محصول
  • دریافت اطلاعات از سرور
  • حذف یک آیتم از لیست
  • کشیدن صفحه برای بروزرسانی (Pull to Refresh)

نحوه تعامل اجزای MVI در اندروید

سه جزء اصلی MVI همیشه در یک چرخه مشخص با یکدیگر تعامل دارند:

  1. کاربر عملی را در رابط کاربری انجام می‌دهد.
  2. View آن عمل را به‌صورت یک Intent ارسال می‌کند.
  3. Model، Intent را پردازش کرده و وضعیت جدید (State) را ایجاد می‌کند.
  4. View، State جدید را دریافت کرده و رابط کاربری را مجدداً نمایش می‌دهد.

این جریان داده یک‌طرفه باعث می‌شود تمام تغییرات برنامه قابل ردیابی، قابل پیش‌بینی و مدیریت باشند؛ ویژگی‌ای که یکی از مهم‌ترین دلایل محبوبیت معماری MVI در توسعه اپلیکیشن‌های مدرن اندروید است.

State در معماری MVI  چیست؟

State یا «وضعیت»، یکی از مهم‌ترین مفاهیم در معماری MVI (Model–View–Intent) است. State نمایانگر وضعیت فعلی رابط کاربری (UI) در یک لحظه مشخص است و تمام اطلاعاتی که برای نمایش صفحه به کاربر نیاز است، در آن نگهداری می‌شود.

برخلاف معماری‌هایی مانند MVVM که ممکن است داده‌های رابط کاربری در چندین LiveData یا StateFlow مختلف ذخیره شوند، در MVI معمولاً تمام وضعیت صفحه در قالب یک State واحد (Single Source of Truth) مدیریت می‌شود. به همین دلیل، رابط کاربری همیشه بر اساس یک منبع مشخص رندر شده و احتمال ناسازگاری بین بخش‌های مختلف UI به حداقل می‌رسد.

معماری MVI

معماری MVI

State چه اطلاعاتی را نگهداری می‌کند؟

State می‌تواند شامل هر اطلاعاتی باشد که برای نمایش رابط کاربری لازم است، مانند:

  • لیست داده‌های نمایش داده شده
  • وضعیت بارگذاری (Loading)
  • پیام‌های خطا (Error)
  • اطلاعات کاربر
  • متن فیلدهای ورودی
  • وضعیت انتخاب آیتم‌ها
  • نتیجه عملیات انجام‌شده

به‌عنوان مثال، فرض کنید صفحه‌ای برای نمایش لیست محصولات دارید. State این صفحه می‌تواند به شکل زیر باشد:

data class ProductState(
    val isLoading: Boolean = false,
    val products: List<Product> = emptyList(),
    val error: String? = null
)

در این مثال، تمام اطلاعات موردنیاز رابط کاربری در یک کلاس داده (Data Class) قرار گرفته است. بنابراین View تنها کافی است آخرین مقدار ProductState را دریافت کرده و بر اساس آن صفحه را نمایش دهد.

چرا State در MVI اهمیت دارد؟

در معماری MVI، رابط کاربری هیچ تغییری را به‌صورت مستقیم اعمال نمی‌کند. هر تعامل کاربر ابتدا به‌صورت یک Intent ارسال می‌شود، سپس منطق برنامه آن را پردازش کرده و در صورت نیاز یک State جدید ایجاد می‌کند. در نهایت، View تنها وظیفه دارد State جدید را نمایش دهد.

این فرآیند مزایای مهمی به همراه دارد:

  • رفتار برنامه قابل پیش‌بینی‌تر می‌شود.
  • مدیریت وضعیت رابط کاربری ساده‌تر خواهد بود.
  • احتمال بروز خطاهای ناشی از تغییر هم‌زمان داده‌ها کاهش پیدا می‌کند.
  • اشکال‌زدایی (Debugging) آسان‌تر می‌شود، زیرا هر تغییر در رابط کاربری نتیجه یک State مشخص است.
  • تست واحد (Unit Testing) ساده‌تر خواهد بود، زیرا می‌توان خروجی هر Intent را با بررسی State جدید ارزیابی کرد.

Immutable State؛ یکی از اصول MVI در اندروید

یکی از اصول مهم در MVI، تغییرناپذیر (Immutable) بودن State است. به این معنا که پس از ایجاد یک State، مقدار آن تغییر نمی‌کند. هر زمان که وضعیت برنامه عوض شود، به‌جای ویرایش State قبلی، یک نمونه جدید از State ساخته می‌شود.

برای مثال:

val newState = currentState.copy(
    isLoading = false,
    products = productList
)

استفاده از Immutable State باعث می‌شود تغییرات برنامه شفاف‌تر، قابل ردیابی‌تر و مدیریت آن‌ها در پروژه‌های بزرگ بسیار ساده‌تر باشد.

ارتباط State با Intent و View

در معماری MVI، این سه بخش همواره در کنار یکدیگر عمل می‌کنند:

View → Intent → Model/Reducer → State → View

در این چرخه، کاربر یک عمل انجام می‌دهد (Intent)، منطق برنامه آن را پردازش می‌کند، State جدید تولید می‌شود و در نهایت رابط کاربری بر اساس همان State دوباره رندر می‌شود. به همین دلیل، State قلب معماری MVI محسوب می‌شود و نقش اصلی را در مدیریت وضعیت و نمایش رابط کاربری ایفا می‌کند.

جریان داده (Unidirectional Data Flow) معماری MVI در اندروید

یکی از مهم‌ترین ویژگی‌های معماری MVI (Model–View–Intent)، استفاده از جریان داده یک‌طرفه (Unidirectional Data Flow) است. در این رویکرد، داده‌ها تنها در یک مسیر مشخص حرکت می‌کنند و هیچ بخشی از برنامه نمی‌تواند به‌صورت مستقیم وضعیت (State) رابط کاربری را تغییر دهد. این ساختار باعث می‌شود رفتار برنامه قابل پیش‌بینی‌تر، مدیریت آن ساده‌تر و احتمال بروز خطاهای ناشی از تغییرات هم‌زمان به حداقل برسد.

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

به عبارت دیگر، View هیچ‌گاه مستقیماً داده‌ها را تغییر نمی‌دهد و تنها وظیفه نمایش State را بر عهده دارد.

مراحل جریان داده در MVI

جریان داده در MVI معمولاً به ترتیب زیر انجام می‌شود:

  1. کاربر با رابط کاربری تعامل می‌کند.
  2. View یک Intent ایجاد می‌کند.
  3. Intent به ViewModel یا لایه پردازش ارسال می‌شود.
  4. منطق برنامه Intent را پردازش می‌کند.
  5. یک State جدید تولید می‌شود.
  6. View آخرین State را دریافت کرده و رابط کاربری را به‌روزرسانی می‌کند.

این چرخه را می‌توان به شکل زیر نمایش داد:

User
   │
   ▼
 View
   │
   ▼
 Intent
   │
   ▼
 ViewModel / Reducer
   │
   ▼
 New State
   │
   ▼
 View (Render UI)

یک مثال ساده

فرض کنید کاربر روی دکمه «ورود» کلیک می‌کند.

  • کاربر روی دکمه کلیک می‌کند.
  • View یک LoginIntent ایجاد می‌کند.
  • ViewModel اطلاعات ورود را بررسی می‌کند.
  • در صورت موفقیت، یک State جدید مانند LoginSuccessState تولید می‌شود.
  • رابط کاربری صفحه اصلی را نمایش می‌دهد.

در این فرآیند، View هیچ تغییری را مستقیماً روی رابط کاربری اعمال نکرده است؛ بلکه تنها نتیجه State جدید را نمایش داده است.

تفاوت MVP با معماری‌های سنتی

در معماری‌هایی مانند MVC یا حتی MVVM، ممکن است چندین بخش مختلف برنامه بتوانند داده‌های رابط کاربری را تغییر دهند. این موضوع در پروژه‌های بزرگ باعث پیچیدگی، ایجاد وضعیت‌های ناسازگار و دشوار شدن اشکال‌زدایی می‌شود.

اما در MVI، تنها یک مسیر برای تغییر State وجود دارد و تمامی تغییرات باید از همان مسیر عبور کنند. به همین دلیل:

  • رفتار برنامه قابل پیش‌بینی‌تر است.
  • مدیریت وضعیت (State Management) ساده‌تر می‌شود.
  • اشکال‌زدایی و تست آسان‌تر خواهد بود.
  • احتمال ایجاد Race Condition و مشکلات هم‌زمانی کاهش پیدا می‌کند.

مزایای جریان داده یک‌ طرفه

استفاده از Unidirectional Data Flow در معماری MVI مزایای متعددی دارد، از جمله:

  • مدیریت متمرکز وضعیت برنامه (Single Source of Truth)
  • کاهش خطاهای ناشی از تغییرات هم‌زمان داده‌ها
  • ساده‌تر شدن فرآیند Debug و Unit Test
  • رفتار قابل پیش‌بینی رابط کاربری
  • سازگاری بسیار خوب با Jetpack Compose و StateFlow
  • نگهداری آسان‌تر پروژه‌های بزرگ و تیمی

به همین دلیل، جریان داده یک‌طرفه یکی از مهم‌ترین دلایل محبوبیت معماری MVI در توسعه اپلیکیشن‌های مدرن اندروید محسوب می‌شود و نقش کلیدی در ایجاد برنامه‌هایی پایدار، مقیاس‌پذیر و قابل نگهداری ایفا می‌کند.

معماری MVI در اندروید چگونه کار می‌کند؟

معماری MVI (Model–View–Intent) بر پایه یک اصل ساده اما قدرتمند بنا شده است: تمام تغییرات رابط کاربری باید از یک مسیر مشخص عبور کنند. به همین دلیل، MVI از جریان داده یک‌طرفه (Unidirectional Data Flow) استفاده می‌کند تا وضعیت (State) برنامه همیشه قابل پیش‌بینی و مدیریت باشد.

در این معماری، هیچ بخشی از رابط کاربری به‌صورت مستقیم داده‌ها را تغییر نمی‌دهد. هر اقدامی که کاربر انجام می‌دهد، ابتدا به شکل یک Intent ثبت می‌شود. سپس این Intent توسط لایه منطق برنامه پردازش شده و در صورت نیاز، یک State جدید ایجاد می‌شود. در نهایت، رابط کاربری بر اساس همین State دوباره رندر (Render) شده و آخرین وضعیت برنامه را نمایش می‌دهد.

به بیان ساده، در MVI رابط کاربری فقط State را نمایش می‌دهد و هیچ مسئولیتی در مدیریت منطق برنامه یا تغییر داده‌ها ندارد.

مراحل عملکرد معماری MVI در اندروید

چرخه اجرای MVI معمولاً به ترتیب زیر انجام می‌شود:

۱. کاربر با رابط کاربری تعامل می‌کند (View)

فرآیند از جایی آغاز می‌شود که کاربر عملی را در برنامه انجام می‌دهد؛ برای مثال:

  • کلیک روی یک دکمه
  • وارد کردن متن
  • انتخاب یک آیتم
  • کشیدن صفحه برای به‌روزرسانی (Pull to Refresh)

در این مرحله View تنها وظیفه دریافت تعاملات کاربر را بر عهده دارد.

۲. ایجاد Intent

هر تعامل کاربر به یک Intent تبدیل می‌شود. Intent در واقع بیان می‌کند که کاربر قصد انجام چه عملی را دارد.

برای مثال:

  • LoadProducts
  • Login
  • RefreshData
  • SearchProduct

Intent هیچ تغییری در رابط کاربری ایجاد نمی‌کند و تنها درخواست کاربر را به لایه منطق برنامه ارسال می‌کند.

۳. پردازش Intent

پس از دریافت Intent، ViewModel یا لایه پردازش (Processor/Reducer) منطق موردنیاز را اجرا می‌کند. این مرحله ممکن است شامل عملیات مختلفی باشد، مانند:

  • فراخوانی API
  • دریافت اطلاعات از پایگاه داده
  • اعتبارسنجی داده‌ها
  • انجام محاسبات
  • پردازش اطلاعات

۴. تولید State جدید

پس از پایان پردازش، نتیجه عملیات در قالب یک State جدید تولید می‌شود.

برای مثال:

  • شروع بارگذاری اطلاعات
  • دریافت موفق لیست کاربران
  • نمایش پیام خطا
  • نمایش اطلاعات کاربر

نکته مهم این است که در MVI، State قبلی تغییر نمی‌کند؛ بلکه یک نمونه جدید از State ایجاد می‌شود. این ویژگی که با نام Immutable State شناخته می‌شود، یکی از اصول اساسی معماری MVI است.

۵. به‌روزرسانی رابط کاربری

در آخرین مرحله، View آخرین State را دریافت کرده و رابط کاربری را دوباره رندر می‌کند.

اگر State نشان دهد که داده‌ها در حال بارگذاری هستند، View یک ProgressBar نمایش می‌دهد. اگر داده‌ها با موفقیت دریافت شده باشند، لیست اطلاعات نمایش داده می‌شود و در صورت بروز خطا نیز پیام مناسب به کاربر نمایش داده خواهد شد.

چرخه کامل اجرای MVI

روند اجرای معماری MVI را می‌توان به شکل زیر نمایش داد:

                User
                  │
                  ▼
               View (UI)
                  │
            ایجاد Intent
                  │
                  ▼
              ViewModel
                  │
         پردازش منطق برنامه
                  │
                  ▼
          Repository / API
                  │
                  ▼
            تولید State جدید
                  │
                  ▼
            Render View (UI)

یک مثال عملی

فرض کنید کاربر روی دکمه «دریافت محصولات» کلیک می‌کند.

  1. کاربر روی دکمه کلیک می‌کند.
  2. View یک LoadProductsIntent ایجاد می‌کند.
  3. ViewModel درخواست را دریافت می‌کند.
  4. Repository اطلاعات را از API دریافت می‌کند.
  5. ViewModel یک ProductState جدید شامل لیست محصولات ایجاد می‌کند.
  6. View State جدید را دریافت کرده و لیست محصولات را روی صفحه نمایش می‌دهد.

در این فرآیند، هیچ بخشی از رابط کاربری مستقیماً داده‌ها را تغییر نمی‌دهد و تمامی تغییرات تنها از طریق Intent و State انجام می‌شوند.

چرخه اجرای یک Intent در معماری MVI

در معماری MVI (Model–View–Intent)، هر تغییری که در رابط کاربری رخ می‌دهد، از یک Intent آغاز می‌شود. Intent در واقع بیان‌کننده قصد یا درخواست کاربر برای انجام یک عملیات است. برای مثال، کلیک روی دکمه ورود، جستجوی یک محصول یا به‌روزرسانی اطلاعات، همگی به‌صورت Intent به لایه منطق برنامه ارسال می‌شوند.

پس از دریافت Intent، برنامه آن را پردازش کرده و نتیجه را در قالب یک State جدید به رابط کاربری بازمی‌گرداند. این چرخه باعث می‌شود تمام تغییرات برنامه از یک مسیر مشخص عبور کنند و وضعیت رابط کاربری همیشه قابل پیش‌بینی باشد.

مراحل اجرای یک Intent

چرخه اجرای یک Intent در MVI معمولاً شامل مراحل زیر است:

۱. تعامل کاربر با رابط کاربری

همه چیز از تعامل کاربر آغاز می‌شود. این تعامل می‌تواند یکی از موارد زیر باشد:

  • کلیک روی یک دکمه
  • وارد کردن متن
  • انتخاب یک گزینه
  • کشیدن صفحه برای به‌روزرسانی
  • لمس یک آیتم در لیست

در این مرحله، View هیچ تغییری در داده‌ها ایجاد نمی‌کند و تنها رویداد کاربر را دریافت می‌کند.

۲. ایجاد Intent

پس از دریافت تعامل، View یک Intent ایجاد می‌کند که نشان‌دهنده درخواست کاربر است.

برای مثال:

sealed class LoginIntent {
    data object Login : LoginIntent()
}

یا برای صفحه محصولات:

sealed class ProductIntent {
    data object LoadProducts : ProductIntent()
}

Intent تنها بیان می‌کند که کاربر چه کاری می‌خواهد انجام دهد و شامل منطق برنامه نیست.

۳. ارسال Intent به ViewModel

View، Intent ایجاد شده را به ViewModel ارسال می‌کند.

viewModel.onIntent(ProductIntent.LoadProducts)

از این لحظه به بعد، مسئولیت پردازش درخواست بر عهده ViewModel خواهد بود.

۴. پردازش Intent

ViewModel با توجه به نوع Intent، عملیات موردنیاز را اجرا می‌کند. این عملیات می‌تواند شامل موارد زیر باشد:

  • فراخوانی API
  • خواندن اطلاعات از پایگاه داده
  • اعتبارسنجی داده‌ها
  • انجام محاسبات
  • ذخیره اطلاعات

در بسیاری از پروژه‌ها، ViewModel این عملیات را به Repository واگذار می‌کند تا منطق دسترسی به داده‌ها از رابط کاربری جدا باشد.

۵. تولید State جدید

پس از پایان پردازش، نتیجه عملیات در قالب یک State جدید ایجاد می‌شود.

برای مثال:

data class ProductState(
    val isLoading: Boolean,
    val products: List<Product>,
    val error: String?
)

اگر عملیات موفق باشد، لیست محصولات در State قرار می‌گیرد و اگر خطایی رخ دهد، پیام خطا در همان State ذخیره می‌شود.

در MVI، State قبلی تغییر نمی‌کند؛ بلکه همیشه یک نمونه جدید از State ساخته می‌شود.

۶. انتشار State

ViewModel State جدید را از طریق StateFlow یا LiveData منتشر می‌کند.

_state.value = ProductState(
    isLoading = false,
    products = products,
    error = null
)

۷. به‌روزرسانی رابط کاربری

در آخرین مرحله، View آخرین State را دریافت کرده و رابط کاربری را دوباره رندر می‌کند.

اگر داده‌ها با موفقیت دریافت شده باشند، لیست نمایش داده می‌شود. اگر عملیات در حال اجرا باشد، نشانگر بارگذاری نمایش داده خواهد شد و در صورت بروز خطا، پیام مناسب به کاربر نمایش داده می‌شود.

نمودار چرخه اجرای Intent

چرخه اجرای یک Intent را می‌توان به شکل زیر نمایش داد:

┌─────────────┐
│    User     │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│    View     │
└──────┬──────┘
       │ Intent
       ▼
┌─────────────┐
│ ViewModel   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ Repository  │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ API / DB    │
└──────┬──────┘
       │ Result
       ▼
┌─────────────┐
│ New State   │
└──────┬──────┘
       │
       ▼
┌─────────────┐
│ Render View │
└─────────────┘

مثال واقعی

فرض کنید کاربر روی دکمه «ورود» کلیک می‌کند:

  1. کاربر دکمه ورود را لمس می‌کند.
  2. View یک LoginIntent ایجاد می‌کند.
  3. ViewModel درخواست ورود را دریافت می‌کند.
  4. اطلاعات کاربر به سرور ارسال می‌شود.
  5. نتیجه ورود دریافت می‌شود.
  6. یک LoginState جدید ایجاد می‌شود.
  7. رابط کاربری بر اساس State جدید، صفحه اصلی یا پیام خطا را نمایش می‌دهد.

این چرخه برای تمامی تعاملات کاربر در معماری MVI تکرار می‌شود. به همین دلیل، تمامی تغییرات برنامه از یک مسیر مشخص عبور می‌کنند و مدیریت وضعیت، اشکال‌زدایی و تست اپلیکیشن بسیار ساده‌تر و قابل پیش‌بینی‌تر خواهد بود.

نمودار معماری MVI

یکی از بهترین روش‌ها برای درک معماری MVI (Model–View–Intent)، مشاهده جریان ارتباط بین اجزای مختلف آن است. در این معماری، تمام تعاملات کاربر از یک مسیر مشخص عبور می‌کنند و در نهایت، یک State جدید برای نمایش در رابط کاربری تولید می‌شود. این رویکرد که با عنوان جریان داده یک‌طرفه (Unidirectional Data Flow) شناخته می‌شود، باعث می‌شود مدیریت وضعیت برنامه ساده‌تر، رفتار آن قابل پیش‌بینی‌تر و فرآیند اشکال‌زدایی آسان‌تر شود.

تفاوت MVI با MVVM

MVI (Model–View–Intent) و MVVM (Model–View–ViewModel) دو مورد از محبوب‌ترین معماری‌های توسعه اپلیکیشن‌های اندرویدی هستند که هر دو با هدف جداسازی منطق برنامه از رابط کاربری، افزایش خوانایی کد و بهبود قابلیت نگهداری پروژه طراحی شده‌اند. با وجود شباهت‌های زیاد، این دو معماری در نحوه مدیریت وضعیت (State)، جریان داده و تعامل بین اجزای برنامه تفاوت‌های مهمی دارند.

اگرچه MVVM سال‌ها به‌عنوان معماری پیشنهادی گوگل شناخته می‌شود، اما MVI با تمرکز بر جریان داده یک‌طرفه (Unidirectional Data Flow) و مدیریت متمرکز State، در پروژه‌های مدرن و به‌ویژه برنامه‌های مبتنی بر Jetpack Compose محبوبیت زیادی پیدا کرده است.

تفاوت MVI با MVVM

تفاوت MVI با MVVM

مقایسه MVI و MVVM

ویژگی MVVM MVI
جریان داده دوطرفه یا ترکیبی یک‌طرفه (Unidirectional Data Flow)
مدیریت وضعیت چندین LiveData یا StateFlow یک State واحد (Single Source of Truth)
تغییر رابط کاربری با تغییر داده‌های ViewModel فقط با ایجاد State جدید
پیش‌بینی رفتار برنامه متوسط بسیار بالا
مدیریت State نسبتاً ساده متمرکز و ساختاریافته
اشکال‌زدایی (Debugging) خوب بسیار ساده‌تر
تست‌پذیری بالا بسیار بالا
مناسب برای پروژه‌های کوچک تا بزرگ پروژه‌های بزرگ، پیچیده و Reactive
سازگاری با Jetpack Compose بسیار خوب عالی

تفاوت در جریان داده

مهم‌ترین تفاوت این دو معماری، نحوه حرکت داده‌ها در برنامه است.

در MVVM، View با ViewModel در ارتباط است و ViewModel نیز داده‌ها را از Model دریافت می‌کند. در برخی سناریوها، ممکن است View چندین منبع داده مانند LiveData یا StateFlow را مشاهده کند و هرکدام در زمان متفاوتی تغییر کنند. این موضوع در پروژه‌های بزرگ می‌تواند مدیریت وضعیت را پیچیده‌تر کند.

در مقابل، MVI از یک جریان داده یک‌طرفه استفاده می‌کند. تمام تعاملات کاربر ابتدا به یک Intent تبدیل می‌شوند، سپس منطق برنامه آن‌ها را پردازش کرده و در نهایت یک State جدید تولید می‌شود. رابط کاربری نیز فقط آخرین State را دریافت کرده و دوباره رندر می‌شود.

به همین دلیل، در MVI تنها یک مسیر برای تغییر وضعیت برنامه وجود دارد و رفتار برنامه کاملاً قابل پیش‌بینی است.

جمع‌بندی

معماری MVI (Model–View–Intent) یکی از مدرن‌ترین الگوهای معماری در توسعه اپلیکیشن‌های اندروید است که با استفاده از جریان داده یک‌طرفه (Unidirectional Data Flow)، مدیریت وضعیت (State) را ساده‌تر و قابل پیش‌بینی‌تر می‌کند. در این معماری، تمام تعاملات کاربر به‌صورت Intent ثبت می‌شوند، سپس منطق برنامه آن‌ها را پردازش کرده و در نهایت یک State جدید برای رابط کاربری تولید می‌شود.

در این مقاله با مفهوم معماری MVI، دلیل به‌وجود آمدن آن، اجزای اصلی (Model، View و Intent)، نحوه عملکرد، مزایا و معایب، تفاوت آن با معماری‌های MVC، MVP و MVVM و همچنین نحوه پیاده‌سازی آن در اندروید آشنا شدیم.

اگر در حال توسعه یک اپلیکیشن کوچک هستید، استفاده از MVI ممکن است در ابتدا کمی پیچیده به نظر برسد. اما برای پروژه‌های متوسط و بزرگ که مدیریت وضعیت، تست‌پذیری و نگهداری کد اهمیت زیادی دارد، MVI می‌تواند انتخابی بسیار مناسب باشد. این معماری به‌ویژه در کنار Jetpack Compose و Kotlin Coroutines عملکرد بسیار خوبی دارد و امروزه بسیاری از توسعه‌دهندگان اندروید از آن در پروژه‌های مدرن استفاده می‌کنند.

در نهایت، انتخاب معماری مناسب به نیازهای پروژه، اندازه تیم، میزان پیچیدگی برنامه و تجربه توسعه‌دهندگان بستگی دارد. آشنایی با MVI به شما کمک می‌کند اپلیکیشن‌هایی با ساختار منظم‌تر، کدهای قابل نگهداری‌تر و رابط کاربری پایدارتر توسعه دهید.

سوالات متداول (FAQ)

معماری MVI چیست؟

MVI (Model–View–Intent) یک الگوی معماری برای توسعه رابط کاربری است که بر پایه جریان داده یک‌طرفه (Unidirectional Data Flow) عمل می‌کند. در این معماری، تمام تعاملات کاربر به Intent تبدیل شده، سپس وضعیت جدید (State) تولید می‌شود و رابط کاربری بر اساس آن به‌روزرسانی می‌شود.

MVI مخفف چیست؟

MVI مخفف Model–View–Intent است:

  • Model: مدیریت داده‌ها و وضعیت برنامه
  • View: نمایش رابط کاربری
  • Intent: نمایش اقدامات و تعاملات کاربر

تفاوت MVI و MVVM چیست؟

در MVVM معمولاً ViewModel می‌تواند چندین رویداد و وضعیت مختلف را مدیریت کند، اما در MVI تنها یک منبع حقیقت (Single Source of Truth) برای وضعیت برنامه وجود دارد و تمام تغییرات از طریق جریان داده یک‌طرفه انجام می‌شوند. این موضوع باعث می‌شود مدیریت State ساده‌تر و قابل پیش‌بینی‌تر باشد.

MVI چه مزایایی دارد؟

مهم‌ترین مزایای معماری MVI عبارت‌اند از:

  • مدیریت ساده و شفاف وضعیت (State)
  • جریان داده یک‌طرفه
  • قابلیت تست‌پذیری بالا
  • اشکال‌زدایی آسان‌تر
  • کاهش خطاهای ناشی از تغییرات هم‌زمان رابط کاربری
  • مناسب برای پروژه‌های بزرگ و پیچیده

معایب معماری MVI چیست؟

در کنار مزایا، MVI محدودیت‌هایی نیز دارد:

  • نیاز به کدنویسی بیشتر نسبت به MVVM
  • یادگیری دشوارتر برای افراد مبتدی
  • افزایش تعداد کلاس‌ها و Stateها
  • احتمال تولید Stateهای متعدد در پروژه‌های بسیار بزرگ

آیا MVI برای همه پروژه‌ها مناسب است؟

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

آیا MVI فقط مخصوص اندروید است؟

خیر. اگرچه MVI در توسعه اندروید محبوبیت زیادی دارد، اما این الگو در سایر فناوری‌ها مانند توسعه وب، iOS و حتی فریم‌ورک‌هایی مانند React و Flutter نیز قابل پیاده‌سازی است.

آیا MVI با Jetpack Compose سازگار است؟

بله. یکی از مهم‌ترین دلایل محبوبیت MVI، هماهنگی بسیار خوب آن با Jetpack Compose است. از آنجا که Compose نیز بر پایه State عمل می‌کند، ترکیب آن با MVI باعث می‌شود رابط کاربری ساده‌تر، واکنش‌گرا و قابل نگهداری‌تر باشد.

در MVI منظور از State چیست؟

State وضعیت فعلی رابط کاربری را نشان می‌دهد. برای مثال، اطلاعات نمایش‌داده‌شده، وضعیت بارگذاری (Loading)، پیام‌های خطا یا موفقیت و هر چیزی که روی صفحه نمایش داده می‌شود، بخشی از State محسوب می‌شود.

آیا گوگل معماری MVI را پیشنهاد می‌کند؟

گوگل معماری خاصی را به‌عنوان استاندارد اجباری معرفی نکرده است، اما استفاده از معماری‌های مبتنی بر مدیریت State و جریان داده یک‌طرفه را برای توسعه اپلیکیشن‌های مدرن، به‌ویژه در کنار Jetpack Compose، توصیه می‌کند. به همین دلیل، MVI در سال‌های اخیر به یکی از محبوب‌ترین گزینه‌ها در میان توسعه‌دهندگان اندروید تبدیل شده است.

دیدگاهتان را بنویسید

نشانی ایمیل شما منتشر نخواهد شد. بخش‌های موردنیاز علامت‌گذاری شده‌اند *