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
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
Par exemple, nous voulons afficher un dialogue offrant un
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
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?
- Nouilles des dépendances étatiques des éléments yuans
- La logique des transitions d'un état d'affichage à un autre sera répandue dans le
présentateur - 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 à
MVI
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
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
- Le spectacle habituel de toasts nous brise
- 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
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:
- Un point d'entrée vers la vue
- Nous avons toujours l'état actuel de l'écran à portée de main
- 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. - Flux de données unidirectionnel
Aimez Android et ne perdez jamais votre motivation!
Liste de mes inspirateurs
- www.youtube.com/watch?v=VsStyq4Lzxo&t=592s - Modèles d'interface utilisateur déclaratifs (Google
I / O'19) - www.youtube.com/watch?v=pXw6r2kAvq8&t=2s - Voyage architectural par Zsolt
Kocsi, Badoo EN
- www.youtube.com/watch?v=hBkQkjWnAjg&t=318s - Comment faire cuire
MVI bien séché pour Android - www.youtube.com/watch?v=0IKHxjkgop4 - Gestion de l'état avec RxJava par Jake
Wharton - hannesdorfmann.com/android/model-view-intent - Article de Hannes Doorfmann