الغرض من هذه المقالة
لن أخوض في كيفية تنفيذ MVI تقنيًا (هناك أكثر من طريقة ولكل منها مزاياها وعيوبها). هدفي الرئيسي في مقال قصير هو إثارة اهتمامك بدراسة هذا الموضوع في المستقبل وربما تشجيعك على تنفيذ هذا النمط في مشاريعك القتالية ، أو على الأقل التحقق منه في واجبك المنزلي.
ما المشكلة التي يمكن أن تواجهها
صديقي العزيز ، دعنا نتخيل هذا الموقف ، لدينا واجهة عرض
نعمل بها:
interface ComplexView {
fun showLoading()
fun hideLoading()
fun showBanner()
fun hideBanner()
fun dataLoaded(names: List<String>)
fun showTakeCreditDialog()
fun hideTakeCreditDialog()
}
للوهلة الأولى ، يبدو أنه لا يوجد شيء معقد. أنت ببساطة تختار كيانًا منفصلاً للعمل مع هذا العرض ، تسميه مقدمًا (فويلا ، ها هو MVP جاهز) ، وهذه
وهنا المقدم نفسه:
interface Presenter {
fun onLoadData(dataKey: String)
fun onLoadCredit()
}
الأمر بسيط ، يسحب العرض طرق مقدم العرض عندما يكون ذلك ضروريًا لتحميل البيانات ، وللمقدم ، بدوره ، الحق في سحب العرض لعرض المعلومات التي تم تحميلها ، وكذلك عرض التقدم. ولكن هنا تظهر
على سبيل المثال ، نريد أن نعرض حوارًا يقدم
view.hideTakeCreditDialog ()
لكن في الوقت نفسه ، يجب ألا تنسى أنه عند عرض مربع حوار ، تحتاج إلى إخفاء التحميل وعدم إظهاره أثناء وجود مربع حوار على الشاشة. بالإضافة إلى ذلك ، هناك طريقة تعرض لافتة ، والتي لا ينبغي أن نتصل بها عندما يكون لدينا مربع حوار معروض (أو نغلق مربع الحوار وفقط بعد عرض الشعار ، كل هذا يتوقف على المتطلبات). لديك الصورة التالية.
لا يجب عليك الاتصال بأي حال من الأحوال:
view.showBanner ()
view.showLoading ()
بينما يظهر الحوار. وبخلاف ذلك ، فإن
الآن دعنا نفكر معك ونفترض أنك ما زلت تريد إظهار لافتة (مثل هذا المطلب من العمل). ماذا تريد أن تتذكر؟
الحقيقة أنه عند استدعاء هذه الطريقة:
view.showBanner ()
تأكد من الاتصال بـ:
view.hideLoading ()
view.hideTakeCreditDialog ()
مرة أخرى ، حتى لا يقفز شيء فوق بقية العناصر على الشاشة ، الاتساق سيء السمعة.
لذا يأتي السؤال ، من سيضربك على يديك إذا فعلت شيئًا خاطئًا؟ الجواب بسيط - لا أحد . في مثل هذا الإدراك ، ليس لديك أي سيطرة على الإطلاق.
ربما ستحتاج في المستقبل إلى إضافة المزيد من الوظائف إلى العرض ، والتي ستكون مرتبطة أيضًا بما هو موجود بالفعل. ما هي العيوب التي نحصل عليها من هذا؟
- المعكرونة من تبعيات الدولة لعناصر اليوان
- سيتم تلطيخ منطق الانتقال من حالة عرض إلى أخرى عبر
مقدم العرض - من الصعب جدًا إضافة حالة شاشة جديدة ، حيث توجد مخاطرة كبيرة أن
تنسى إخفاء شيء ما قبل عرض لافتة أو حوار جديد
وقد كنت أنا وأنت من حلل الحالة عندما كان هناك 7 طرق فقط في العرض. وحتى هنا تبين أنها مشكلة.
لكن هناك مثل هذه الآراء:
interface ChatView : IView<ChatPresenter> {
fun setMessage(message: String)
fun showFullScreenProgressBar()
fun updateExistingMessage(model: ChatMessageModel)
fun hideFullScreenProgressBar()
fun addNewMessage(localMessage: ChatMessageModel)
fun showErrorFromLoading(message: String)
fun moveChatToStart()
fun containsMessage(message: ChatMessageModel): Boolean
fun getChatMessagesSize(): Int fun getLastMessage(): ChatMessageModel?
fun updateMessageStatus(messageId: String, status: ChatMessageStatus)
fun setAutoLoading(autoLoadingEnabled: Boolean)
fun initImageInChat(needImageInChat: Boolean)
fun enableNavigationButton()
fun hideKeyboard()
fun scrollToFirstMessage()
fun setTitle(@StringRes titleRes: Int)
fun setVisibleSendingError(isVisible: Boolean)
fun removeMessage(localId: String)
fun setBottomPadding(hasPadding: Boolean)
fun initMessagesList(pageSize: Int)
fun showToast(@StringRes textRes: Int)
fun openMessageDialog(message: String)
fun showSuccessRating()
fun setRatingAvailability(isEnabled: Boolean)
fun showSuccessRatingWithResult(ratingValue: String)
}
سيكون من الصعب جدًا إضافة شيء جديد هنا أو تعديل القديم ، عليك أن ترى ما هو متصل وكيف ، ثم تبدأ في
MVI
بيت القصيد
خلاصة القول هي أن لدينا كيانًا يسمى دولة. بناءً على هذه الحالة ، سيعرض العرض. لن أتعمق في الأمر ، لذا فإن مهمتي هي إثارة اهتمامك ، لذلك سأذهب مباشرة إلى الأمثلة. وفي نهاية المقال ستكون هناك قائمة بالمصادر المفيدة للغاية إذا كنت مهتمًا.
دعونا نتذكر موقفنا في بداية المقال ، لدينا وجهة نظر نعرض عليها الحوارات واللافتات
data class UIState(
val loading: Boolean = false,
val names: List<String>? = null,
val isBannerShowing: Boolean = false,
val isCreditDialogShowing: Boolean = false
)
دعنا نضع القاعدة ، أنا وأنت لا نستطيع تغيير العرض إلا بمساعدة هذه الحالة ، ستكون هناك واجهة من هذا القبيل:
interface ComplexView {
fun renderState(state: UIState)
}
الآن دعونا نضع قاعدة أخرى. يمكننا الاتصال بمالك الدولة (في حالتنا سيكون مقدم العرض) فقط من خلال نقطة دخول واحدة. عن طريق إرسال الأحداث إليه. من الجيد أن نطلق على هذه الأحداث إجراءات.
sealed class UIAction {
class LoadNamesAction(dataKey: String) : UIAction()
object LoadBannerAction : UIAction()
object LoadCreditDialogInfo : UIAction()
}
فقط لا ترمي الطماطم على الفصول المختومة ، فهي تبسط الحياة في الوضع الحالي ، وتزيل الطبقات الإضافية عند معالجة الإجراءات في مقدم العرض ، سيكون المثال أدناه. ستبدو واجهة مقدم العرض كما يلي:
interface Presenter {
fun processAction(action: UIAction)
}
لنفكر الآن في كيفية توصيل كل شيء:
fun processAction(action: UiAction): UIState {
return when (action) {
is UiAction.LoadNamesAction -> state.copy(
loading = true,
isBannerShowing = false,
isCreditDialogShowing = false
)
is UiAction.LoadBannerAction -> state.copy(
loading = false,
isBannerShowing = true,
isCreditDialogShowing = false
)
is UiAction.LoadCreditDialogInfo -> state.copy(
loading = false,
isBannerShowing = false,
isCreditDialogShowing = true
)
}
}
إذا انتبهت ، فإن التدفق من حالة عرض إلى أخرى يحدث الآن في مكان واحد ، ومن الأسهل بالفعل تجميع صورة لكيفية عمل كل شيء في رأسك.
ليس الأمر سهلاً للغاية ، لكن يجب أن تكون حياتك أسهل. بالإضافة إلى ذلك ، في المثال الخاص بي ، هذا غير مرئي ، ولكن يمكننا تحديد كيفية معالجة حالتنا الجديدة بناءً على الحالة السابقة (هناك أيضًا عدة مفاهيم لتنفيذ ذلك). ناهيك عن قابلية إعادة الاستخدام المجنونة التي حققها الرجال في badoo ، كان أحد مساعديهم في تحقيق هذا الهدف هو MVI.
ومع ذلك ، يجب ألا تفرح مبكرًا ، فكل شيء في هذا العالم له إيجابيات وسلبيات ، وها هم
- عرض الخبز المحمص المعتاد يكسرنا
- عند تحديث مربع اختيار واحد ، سيتم نسخ الحالة بأكملها مرة أخرى وإرسالها إلى
العرض ، أي ستحدث إعادة رسم غير ضرورية إذا لم يتم فعل أي شيء حيال ذلك
لنفترض أننا نريد عرض نخب android عادي ، وفقًا للمنطق الحالي ، سنضع علمًا في حالتنا لعرض نخبنا.
data class UIState(
val showToast: Boolean = false,
)
الأول
نأخذ الحالة ونغيرها في مقدم العرض ، ونضبط showToast = true وأبسط شيء يمكن أن يحدث هو تدوير الشاشة. تم تدمير كل شيء ، وإعادة إنشاء
حسنًا ، الثانية
هذه بالفعل مشكلة عرض غير ضروري في العرض ، والتي ستحدث في كل مرة حتى عندما يتغير أحد الحقول فقط في الحالة. ويتم حل هذه المشكلة بعدة طرق ليست أجملها أحيانًا (أحيانًا عن طريق فحص ممل قبل الشكوى من معنى جديد ، أنه مختلف عن السابق). ولكن مع إصدار التأليف في نسخة مستقرة ، سيتم حل هذه المشكلة ، ثم سيعيش صديقي معك في عالم متحول وسعيد!
حان وقت المحترفين:
- نقطة دخول واحدة إلى المنظر
- لدينا دائمًا الحالة الحالية للشاشة في متناول اليد
- حتى في مرحلة التنفيذ ، عليك التفكير في كيفية تدفق دولة إلى دولة
أخرى وما هي الصلة بينهما - تدفق البيانات أحادي الاتجاه
أحب الروبوت ولا تفقد الدافع الخاص بك!
قائمة الملهمين بلدي
- www.youtube.com/watch؟v=VsStyq4Lzxo&t=592s - أنماط واجهة المستخدم
التقريبية (Google I / O'19) - www.youtube.com/watch؟v=pXw6r2kAvq8&t=2s - رحلة معمارية بواسطة Zsolt
Kocsi ، Badoo EN
- www.youtube.com/watch؟v=hBkQkjWnAjg&t=318s - كيفية طهي
MVI جيد الصنع لنظام Android - www.youtube.com/watch؟v=0IKHxjkgop4 - إدارة الحالة باستخدام RxJava بواسطة Jake
Wharton - hannesdorfmann.com/android/model-view-intent - مقال بقلم هانيس دورفمان