本文目的
我不会深入研究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。通过这种实现,您绝对无法控制。
也许将来您需要向视图添加更多功能,这些功能也将与已经存在的功能相关。我们从中得到的不利之处是什么?
- 面条对元元素的依赖
- 从一种显示状态过渡到另一种显示状态的逻辑将在
演示者中被涂抹 - 添加新的屏幕状态非常困难,因为
在显示新的横幅或对话框之前,很容易忘记隐藏某些东西。
当视图中只有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是实现这一目标的助手之一。
但是,您不应该过早为之欢欣,这个世界上的每件事都有利弊,而在这里
- 平常的烤面包表演让我们失望
- 当您更新一个复选框时,整个状态将被再次复制并发送到
视图,也就是说,如果不对其进行任何处理,将发生不必要的重绘
假设我们要显示一个普通的android Toast,根据当前逻辑,我们将在状态下设置一个标志以显示我们的Toast。
data class UIState(
val showToast: Boolean = false,
)
首先
我们在演示者中更改状态,设置showToast = true,最简单的事情就是屏幕旋转。一切都被破坏了,活动的
好吧,第二
这已经是视图中不必要渲染的问题,即使状态中只有一个字段发生更改,每次也会发生这种渲染。这个问题可以通过几种有时不是最漂亮的方法来解决(有时会在抱怨新含义之前通过无聊的检查来解决,这不同于以前的含义)。但是随着compose稳定版本的发布,这个问题将得到解决,然后我的朋友将与您一起生活在一个充满变化和幸福的世界中!
优点时间:
- 视图的一个入口点
- 我们总是拥有屏幕的当前状态
- 即使在实施阶段,您也必须考虑一种状态如何
流入另一种状态以及它们之间的联系是什么 - 单向数据流
喜欢android,永远不会失去动力!
我的启发者名单
- www.youtube.com/watch?v=VsStyq4Lzxo&t=592s-声明性UI模式(Google
I / O'19) - www.youtube.com/watch?v=pXw6r2kAvq8&t=2s-
巴杜Zsolt Kocsi的建筑之旅
- www.youtube.com/watch?v=hBkQkjWnAjg&t=318s-如何
为Android编写出色的MVI - www.youtube.com/watch?v=0IKHxjkgop4-使用RxJava管理状态作者:Jake
Wharton - hannesdorfmann.com/android/model-view-intent-Hannes Doorfmann的文章