فلماذا نحتاج إلى MVI في تطوير الأجهزة المحمولة؟

لقد قيل الكثير بالفعل حول MVI ، حول كيفية قليها وتكوينها بشكل صحيح. ومع ذلك ، لا يتم تخصيص الكثير من الوقت لكيفية تبسيط هذه الطريقة للحياة في مواقف معينة ، مقارنة بالنهج الأخرى.



الغرض من هذه المقالة



لن أخوض في كيفية تنفيذ 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 ()



مرة أخرى ، حتى لا يقفز شيء فوق بقية العناصر على الشاشة ، الاتساق سيء السمعة.



لذا يأتي السؤال ، من سيضربك على يديك إذا فعلت شيئًا خاطئًا؟ الجواب بسيط - لا أحد . في مثل هذا الإدراك ، ليس لديك أي سيطرة على الإطلاق.



ربما ستحتاج في المستقبل إلى إضافة المزيد من الوظائف إلى العرض ، والتي ستكون مرتبطة أيضًا بما هو موجود بالفعل. ما هي العيوب التي نحصل عليها من هذا؟



  1. المعكرونة من تبعيات الدولة لعناصر اليوان
  2. سيتم تلطيخ منطق الانتقال من حالة عرض إلى أخرى عبر

    مقدم العرض
  3. من الصعب جدًا إضافة حالة شاشة جديدة ، حيث توجد مخاطرة كبيرة أن

    تنسى إخفاء شيء ما قبل عرض لافتة أو حوار جديد


وقد كنت أنا وأنت من حلل الحالة عندما كان هناك 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.



ومع ذلك ، يجب ألا تفرح مبكرًا ، فكل شيء في هذا العالم له إيجابيات وسلبيات ، وها هم



  1. عرض الخبز المحمص المعتاد يكسرنا
  2. عند تحديث مربع اختيار واحد ، سيتم نسخ الحالة بأكملها مرة أخرى وإرسالها إلى

    العرض ، أي ستحدث إعادة رسم غير ضرورية إذا لم يتم فعل أي شيء حيال ذلك


لنفترض أننا نريد عرض نخب android عادي ، وفقًا للمنطق الحالي ، سنضع علمًا في حالتنا لعرض نخبنا.



data class UIState(
       val showToast: Boolean = false, 
)


الأول



نأخذ الحالة ونغيرها في مقدم العرض ، ونضبط showToast = true وأبسط شيء يمكن أن يحدث هو تدوير الشاشة. تم تدمير كل شيء ، وإعادة إنشاء الانفجارات وتدمير النشاط ، ولكن نظرًا لأنك مطور رائع ، فإن حالتك تمر بهذا الأمر برمته. وفي الولاية لدينا علم سحري يقول لعرض الخبز المحمص. النتيجة - يظهر الخبز المحمص مرتين. هناك عدة طرق لحل هذه المشكلة ، وكلها تبدو مثل العكازات . مرة أخرى ، سيتم كتابة هذا في المصادر المرفقة بهذه المقالة.



حسنًا ، الثانية



هذه بالفعل مشكلة عرض غير ضروري في العرض ، والتي ستحدث في كل مرة حتى عندما يتغير أحد الحقول فقط في الحالة. ويتم حل هذه المشكلة بعدة طرق ليست أجملها أحيانًا (أحيانًا عن طريق فحص ممل قبل الشكوى من معنى جديد ، أنه مختلف عن السابق). ولكن مع إصدار التأليف في نسخة مستقرة ، سيتم حل هذه المشكلة ، ثم سيعيش صديقي معك في عالم متحول وسعيد!



حان وقت المحترفين:



  1. نقطة دخول واحدة إلى المنظر
  2. لدينا دائمًا الحالة الحالية للشاشة في متناول اليد
  3. حتى في مرحلة التنفيذ ، عليك التفكير في كيفية تدفق دولة إلى دولة

    أخرى وما هي الصلة بينهما
  4. تدفق البيانات أحادي الاتجاه


أحب الروبوت ولا تفقد الدافع الخاص بك!



قائمة الملهمين بلدي








All Articles