Alors pourquoi avons-nous besoin de MVI dans le développement mobile?

On a déjà beaucoup parlé de MVI, comment bien le faire frire et le configurer. Cependant, peu de temps est consacré à la façon dont cette méthode simplifie la vie dans certaines situations, par rapport à d'autres approches.



Objectif de cet article



Je n'entrerai pas dans la manière dont MVI est implémenté techniquement (il y a plus d'un moyen et chacun a ses propres avantages et inconvénients). Mon objectif principal dans un court article est de vous intéresser à étudier ce sujet à l'avenir et éventuellement de vous encourager à mettre en œuvre ce schéma sur vos projets de combat, ou du moins à le vérifier sur vos devoirs.



Quel problème pouvez-vous affronter



Mon cher ami, imaginons cette situation, nous avons une interface de visualisation avec laquelle

travailler:



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


À première vue, il semble que rien n'est compliqué. Vous venez de sélectionner une entité distincte pour travailler avec cette vue, appelez-la un présentateur (voila, voici le MVP), et ce sont de gros problèmes, de petites difficultés, et maintenant je vais essayer d'expliquer pourquoi.



Et voici le présentateur lui-même:



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


C'est simple, la vue tire les méthodes du présentateur lorsqu'il est nécessaire de charger des données, le présentateur, à son tour, a le droit de tirer la vue afin d'afficher les informations chargées, ainsi que d'afficher la progression. Mais ici, le problème de la complexité apparaît - c'est le manque absolu de contrôle sur la cohérence de votre interface utilisateur, mon ami.



Par exemple, nous voulons afficher un dialogue offrant un prêt une offre avantageuse à l'utilisateur et faire cet appel du présentateur, ayant un lien vers l'interface de visualisation entre nos mains:



view.hideTakeCreditDialog ()



Mais en même temps, vous ne devez pas oublier que lors de l'affichage d'une boîte de dialogue, vous devez masquer le chargement et ne pas l'afficher pendant que vous avez une boîte de dialogue à l'écran. De plus, il existe une méthode qui affiche une bannière, que nous ne devons pas appeler pendant que nous affichons une boîte de dialogue (ou fermer la boîte de dialogue et seulement après cela afficher la bannière, tout dépend des exigences). Vous avez l'image suivante.



En aucun cas vous ne devez appeler:



view.showBanner ()



view.showLoading ()



Pendant que le dialogue s'affiche. Sinon, les chats, les testeurs et les utilisateurs, pleureront de douleur dans leurs yeux d'un seul coup d'œil sur une bannière sur un dialogue important avec un prêt avec une offre rentable.



Maintenant, réfléchissons avec vous et supposons que vous vouliez toujours montrer une bannière (une telle exigence d'une entreprise). De quoi devez-vous vous souvenir?

Le fait est que lors de l'appel de cette méthode:



view.showBanner ()



Assurez-vous d'appeler:



view.hideLoading ()



view.hideTakeCreditDialog ()



Encore une fois, pour que rien ne saute au-dessus des autres éléments à l'écran, la consistance notoire.



Alors la question se pose, qui vous frappera sur les mains si vous faites quelque chose de mal? La réponse est simple - PERSONNE . Dans une telle réalisation, vous n'avez absolument aucun contrôle.



Vous devrez peut-être à l'avenir ajouter des fonctionnalités supplémentaires à la vue, qui seront également liées à ce qui existe déjà. Quels sont les inconvénients que nous en retirons?



  1. Nouilles des dépendances étatiques des éléments yuans
  2. La logique des transitions d'un état d'affichage à un autre sera répandue dans le

    présentateur
  3. Il est assez difficile d'ajouter un nouvel état d'écran, car il y a un risque élevé que

    vous oubliez de cacher quelque chose avant d'afficher une nouvelle bannière ou une nouvelle boîte de dialogue


Et c'est vous et moi qui avons analysé le cas alors qu'il n'y a que 7 méthodes dans la vue. Et même ici, cela s'est avéré être un problème.



Mais il y a de telles vues:



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


Il sera assez difficile d'ajouter quelque chose de nouveau ici ou de modifier l'ancien, vous devez voir ce qui est connecté et comment, puis commencer à pleurer pour travailler et prier pour que le testeur ne manque rien. Et au moment de votre désespoir, il apparaît.



MVI





image

Le point entier



L'essentiel est que nous avons une entité appelée État. En fonction de cet état, la vue rendra son affichage. Je ne vais pas aller plus loin, donc ma tâche est de susciter votre intérêt, je vais donc passer directement aux exemples. Et à la fin de l'article, il y aura une liste de sources très utiles si vous êtes intéressé.



Souvenons-nous de notre position au début de l'article, nous avons une vue sur laquelle nous montrons des dialogues, des bannières et de la magie . Nous décrirons comment vous et moi exprimons notre point de vue



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


Définissons la règle, vous et moi ne pouvons changer la vue qu'à l'aide de cet état, il y aura une telle interface:



interface ComplexView { 
   fun renderState(state: UIState)
}


Maintenant, définissons une autre règle. Nous ne pouvons contacter le propriétaire de l'état (dans notre cas, il s'agira d'un présentateur) que par un seul point d'entrée. En lui envoyant des événements. C'est une bonne idée d'appeler ces événements des actions.



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


Ne me lancez pas de tomates pour les classes scellées, elles simplifient la vie dans la situation actuelle, éliminant les castes supplémentaires lors du traitement des actions dans le présentateur, un exemple sera ci-dessous. L'interface du présentateur ressemblera à ceci:



interface Presenter { 
   fun processAction(action: UIAction)
}


Réfléchissons maintenant à la façon de connecter le tout:



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


Si vous avez fait attention, le flux d'un état d'affichage à un autre se produit maintenant à un endroit et il est déjà plus facile de rassembler une image de la façon dont tout fonctionne dans votre tête.



Ce n'est pas super facile, mais votre vie devrait être plus facile. De plus, dans mon exemple, ce n'est pas visible, mais nous pouvons décider comment traiter notre nouvel état en fonction de l'état précédent (il existe également plusieurs notions pour l'implémenter). Sans parler de la réutilisation insensée que les gars de badoo ont obtenue, l'un de leurs assistants pour atteindre cet objectif était MVI.



Cependant, vous ne devriez pas vous réjouir tôt, tout dans ce monde a à la fois des avantages et des inconvénients, et les voici



  1. Le spectacle habituel de toasts nous brise
  2. Lorsque vous mettez à jour une case à cocher, l'état entier est à nouveau copié et envoyé à la

    vue, c'est-à-dire qu'une mise à jour inutile se produit si rien n'est fait à ce sujet


Supposons que nous voulions afficher un toast Android normal, selon la logique actuelle, nous définirons un drapeau dans notre état pour afficher notre toast.



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


La première



Nous prenons et modifions l'état dans le présentateur, définissons showToast = true et la chose la plus simple qui puisse arriver est la rotation de l'écran. Tout est détruit, les explosions et la destruction de l' activité sont recréées, mais comme vous êtes un développeur cool, votre état traverse tout cela. Et dans l'état, nous avons un drapeau magique qui dit d'afficher des toasts. Résultat - le pain grillé est affiché deux fois. Il existe plusieurs façons de résoudre ce problème, et elles ressemblent toutes à des béquilles . Encore une fois, cela sera écrit dans les sources jointes à cet article.



Eh bien, le second



C'est déjà un problème de rendu inutile dans la vue, qui se produira à chaque fois même lorsqu'un seul des champs de l'état change. Et ce problème est résolu de plusieurs manières parfois pas les plus belles (parfois par une vérification sourde avant de se plaindre d'un nouveau sens, qu'il est différent du précédent). Mais avec la sortie de compose dans une version stable, ce problème sera résolu, alors mon ami vivra avec vous dans un monde transformé et heureux!



Temps pour les pros:



  1. Un point d'entrée vers la vue
  2. Nous avons toujours l'état actuel de l'écran à portée de main
  3. Même au stade de la mise en œuvre, vous devez réfléchir à la manière dont un état se

    transforme en un autre et à quel lien existe-t-il.
  4. Flux de données unidirectionnel


Aimez Android et ne perdez jamais votre motivation!



Liste de mes inspirateurs








All Articles