Warum brauchen wir MVI in der mobilen Entwicklung?

Es wurde bereits viel über MVI gesagt, wie man es richtig brät und konfiguriert. Es wird jedoch nicht viel Zeit darauf verwendet, wie diese Methode das Leben in bestimmten Situationen im Vergleich zu anderen Ansätzen vereinfacht.



Zweck dieses Artikels



Ich werde nicht näher darauf eingehen, wie MVI technisch implementiert ist (es gibt mehr als einen Weg und jeder hat seine eigenen Vor- und Nachteile). Mein Hauptziel in einem kurzen Artikel ist es, Sie zu interessieren, dieses Thema in Zukunft zu studieren und Sie möglicherweise zu ermutigen, dieses Muster in Ihren Kampfprojekten umzusetzen oder es zumindest in hausgemachten Vorbereitungen zu überprüfen.



Welchem ​​Problem können Sie begegnen?



Mein lieber Freund, stellen wir uns diese Situation vor, wir haben eine Ansichtsoberfläche, mit der wir

arbeiten können:



interface ComplexView { 
   fun showLoading()   
   fun hideLoading()   
   fun showBanner()    
   fun hideBanner()    
   fun dataLoaded(names: List<String>)    
   fun showTakeCreditDialog()
   fun hideTakeCreditDialog()
}


Auf den ersten Blick scheint nichts kompliziert zu sein. Sie wählen einfach eine separate Entität aus, um mit dieser Ansicht zu arbeiten, nennen sie einen Moderator (voila, der MVP ist bereit), und dies sind große Probleme, kleine Schwierigkeiten, und jetzt werde ich versuchen zu erklären, warum.



Und hier ist der Moderator selbst:



interface Presenter {  
   fun onLoadData(dataKey: String)    
   fun onLoadCredit()
}


Es ist ganz einfach: In der Ansicht werden die Methoden des Präsentators abgerufen, wenn Daten geladen werden müssen. Der Präsentator hat seinerseits das Recht, die Ansicht abzurufen, um die geladenen Informationen sowie den Fortschritt anzuzeigen. Aber hier tritt das Problem der Komplexität auf - dies ist der absolute Mangel an Kontrolle über die Konsistenz Ihrer Benutzeroberfläche, mein Freund.



Zum Beispiel möchten wir einen Dialog anzeigen , der dem Benutzer ein günstiges Angebot für ein Darlehen bietet, und diesen Anruf vom Präsentator aus tätigen, wobei wir einen Link zur Ansichtsoberfläche in unseren Händen haben:



view.hideTakeCreditDialog ()



Gleichzeitig sollten Sie jedoch nicht vergessen, dass Sie beim Anzeigen eines Dialogfelds das Laden ausblenden und nicht anzeigen müssen, während ein Dialogfeld auf dem Bildschirm angezeigt wird. Außerdem gibt es eine Methode, mit der ein Banner angezeigt wird, die wir nicht aufrufen sollten, während ein Dialogfeld angezeigt wird (oder das Dialogfeld schließen und erst danach das Banner anzeigen, hängt alles von den Anforderungen ab). Sie haben das folgende Bild.



In keinem Fall sollten Sie anrufen:



view.showBanner ()



view.showLoading ()



Während der Dialog angezeigt wird. Andernfalls werden Katzen, Tester und Benutzer bei einem Blick auf ein Banner über einen wichtigen Dialog mit einem Darlehen mit einem profitablen Angebot vor Schmerzen in den Augen weinen .



Und jetzt denken wir mit Ihnen und nehmen an, dass ich trotzdem ein Banner zeigen wollte (eine solche Anforderung von einem Unternehmen). Woran müssen Sie sich erinnern?

Tatsache ist, dass beim Aufrufen dieser Methode:



view.showBanner ()



Rufen Sie unbedingt an:



view.hideLoading ()



view.hideTakeCreditDialog ()



Wiederum, damit nichts über die anderen Elemente auf dem Bildschirm sprang, die berüchtigte Konsistenz.



Es stellt sich also die Frage, wer Sie auf die Hände schlägt, wenn Sie etwas falsch machen. Die Antwort ist einfach - NIEMAND . In einer solchen Erkenntnis haben Sie absolut keine Kontrolle.



Vielleicht müssen Sie in Zukunft der Ansicht weitere Funktionen hinzufügen, die sich auch auf das beziehen, was bereits vorhanden ist. Was sind die Nachteile, die wir daraus ziehen?



  1. Nudeln aus staatlichen Abhängigkeiten von Yuan-Elementen
  2. Die Logik der Übergänge von einem Anzeigezustand in einen anderen wird über den

    Präsentator verschmiert
  3. Es ist ziemlich schwierig, einen neuen Bildschirmstatus hinzuzufügen, da ein hohes Risiko besteht, dass

    Sie vergessen, etwas auszublenden, bevor Sie ein neues Banner oder einen neuen Dialog anzeigen


Und Sie und ich haben den Fall analysiert, wenn nur 7 Methoden in der Ansicht sind. Und auch hier stellte sich heraus, dass es ein Problem war.



Aber es gibt solche Ansichten:



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)
}


Es wird ziemlich schwierig sein, hier etwas Neues hinzuzufügen oder das Alte zu bearbeiten. Man muss sehen, was und wie miteinander verbunden ist, und dann anfangen zu weinen, um zu arbeiten und zu beten, dass der Tester nichts verpasst. Und im Moment Ihrer Verzweiflung erscheint er.



MVI





Bild

Der springende Punkt



Die Quintessenz ist, dass wir eine Entität haben, die als Staat bezeichnet wird. Basierend auf diesem Status wird die Ansicht in der Ansicht gerendert. Ich werde nicht tief gehen, also ist es meine Aufgabe, Ihr Interesse zu wecken, also werde ich direkt zu Beispielen gehen. Und am Ende des Artikels finden Sie eine Liste sehr nützlicher Quellen, wenn Sie interessiert sind.



Erinnern wir uns an unsere Position am Anfang des Artikels. Wir haben eine Ansicht, in der wir Dialoge, Banner und Magie zeigen . Wir werden beschreiben, wie Sie und ich Ansichten äußern



data class UIState(
       val loading: Boolean = false, 
       val names: List<String>? = null,     
       val isBannerShowing: Boolean = false,     
       val isCreditDialogShowing: Boolean = false 
)


Lassen Sie uns die Regel festlegen, Sie und ich können die Ansicht nur mit Hilfe dieses Status ändern, es wird eine solche Schnittstelle geben:



interface ComplexView { 
   fun renderState(state: UIState)
}


Jetzt setzen wir noch eine Regel. Wir können den Eigentümer des Staates (in unserem Fall den Moderator) nur über einen Einstiegspunkt kontaktieren. Durch das Senden von Ereignissen an ihn. Es ist eine gute Idee, diese Ereignisse als Aktionen zu bezeichnen.



sealed class UIAction {     
       class LoadNamesAction(dataKey: String) : UIAction()     
       object LoadBannerAction : UIAction()     
       object LoadCreditDialogInfo : UIAction()
 }


Wirf nur keine Tomaten für versiegelte Klassen auf mich, sie vereinfachen das Leben in der aktuellen Situation und eliminieren zusätzliche Kasten bei der Verarbeitung von Aktionen im Moderator. Ein Beispiel finden Sie weiter unten. Die Presenter-Oberfläche sieht folgendermaßen aus:



interface Presenter { 
   fun processAction(action: UIAction)
}


Lassen Sie uns nun darüber nachdenken, wie Sie das Ganze verbinden können:



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
    )     
  } 
}


Wenn Sie darauf geachtet haben, findet der Fluss von einem Anzeigezustand zum anderen jetzt an einem Ort statt und es ist bereits einfacher, ein Bild davon zusammenzustellen, wie alles in Ihrem Kopf funktioniert.



Es ist nicht super einfach, aber dein Leben sollte einfacher sein. In meinem Beispiel ist dies nicht sichtbar, aber wir können entscheiden, wie unser neuer Status basierend auf dem vorherigen Status verarbeitet werden soll (es gibt auch mehrere Begriffe, um dies zu implementieren). Ganz zu schweigen von der wahnsinnigen Wiederverwendbarkeit, die die Jungs von badoo erreicht haben. Einer ihrer Assistenten bei der Erreichung dieses Ziels war MVI.



Sie sollten sich jedoch nicht früh freuen, alles auf dieser Welt hat Vor- und Nachteile, und hier sind sie



  1. Die übliche Toastshow bricht uns
  2. Wenn Sie ein Kontrollkästchen aktualisieren, wird der gesamte Status erneut kopiert und an die

    Ansicht gesendet. Das heißt, es wird unnötig neu gezeichnet, wenn nichts dagegen unternommen wird


Angenommen, wir möchten einen normalen Android-Toast anzeigen. Gemäß der aktuellen Logik setzen wir in unserem Status ein Flag, um unseren Toast anzuzeigen.



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


Der Erste



Wir nehmen und ändern den Status im Presenter, setzen showToast = true und das Einfachste, was passieren kann, ist die Bildschirmdrehung. Alles wird zerstört, Explosionen und die Zerstörung der Aktivität werden neu erstellt, aber da Sie ein cooler Entwickler sind, durchläuft Ihr Staat diese ganze Sache. Und im Staat haben wir eine magische Flagge, die sagt, Toast anzuzeigen. Ergebnis - Toast wird zweimal angezeigt. Es gibt verschiedene Möglichkeiten, dieses Problem zu lösen, und alle sehen aus wie Krücken . Auch dies wird in den diesem Artikel beigefügten Quellen geschrieben.



Nun, der zweite



Dies ist bereits ein Problem des unnötigen Renderns in der Ansicht, das jedes Mal auftritt, selbst wenn sich nur eines der Felder im Status ändert. Und dieses Problem wird auf mehrere, manchmal nicht die schönsten Arten gelöst (manchmal durch eine langweilige Prüfung, bevor man sich über die neue Bedeutung beschwert, dass sie sich von der vorherigen unterscheidet). Aber mit der Veröffentlichung von compose in einer stabilen Version wird dieses Problem gelöst, dann wird mein Freund mit Ihnen in einer verwandelten und glücklichen Welt leben!



Zeit für die Profis:



  1. Ein Einstiegspunkt in die Ansicht
  2. Wir haben immer den aktuellen Status des Bildschirms zur Hand
  3. Bereits in der Implementierungsphase müssen Sie darüber nachdenken, wie ein Zustand

    in einen anderen fließt und welche Verbindung zwischen ihnen besteht
  4. Unidirektionaler Datenfluss


Liebe Android und verliere nie deine Motivation!



Liste meiner Inspiratoren








All Articles