那么,为什么在移动开发中需要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()



再次强调,臭名昭著的一致性使任何内容都不会跳过屏幕上的其余元素。



所以问题来了,如果你做错了什么,谁会打你?答案很简单-NOBODY通过这种实现,您绝对无法控制。



也许将来您需要向视图添加更多功能,这些功能也将与已经存在的功能相关。我们从中得到的不利之处是什么?



  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 Toast,根据当前逻辑,我们将在状态下设置一个标志以显示我们的Toast。



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


首先



我们在演示者中更改状态,设置showToast = true,最简单的事情就是屏幕旋转。一切都被破坏了,活动的爆炸式增长和破坏被重新创建,但是由于您是一个出色的开发人员,因此您的状态将贯穿整个过程。在该状态下,我们有一个魔术旗说要烤面包。结果-烤面包显示两次。有几种解决此问题的方法,它们看起来都像拐杖同样,这将写在本文所附的资源中。



好吧,第二



这已经是视图中不必要渲染的问题,即使状态中只有一个字段发生更改,每次也会发生这种渲染。这个问题可以通过几种有时不是最漂亮的方法来解决(有时会在抱怨新含义之前通过无聊的检查来解决,这不同于以前的含义)。但是随着compose稳定版本的发布,这个问题将得到解决,然后我的朋友将与您一起生活在一个充满变化和幸福的世界中!



优点时间:



  1. 视图的一个入口点
  2. 我们总是拥有屏幕的当前状态
  3. 即使在实施阶段,您也必须考虑一种状态如何

    流入另一种状态以及它们之间的联系是什么
  4. 单向数据流


喜欢android,永远不会失去动力!



我的启发者名单








All Articles