Talk about the insights after using the MVI architecture in two commercial projects

foreword

MVI is not a new thing, and in 2020, I had an idea to discuss one or two with readers by writing an article.

At that time, the project adopted the MVP layered design, and the code styles of the team members were quite different. The assignment of class responsibilities and the encapsulation style in the code were separate sets. With the rapid expansion of the business, the code became more and more chaotic. Try to use MVI架构+ to 单向流form 掣肘a consistent style. However, this approach is not people-oriented enough, and finally solved the problem by "proper transformation on the basis of MVP + design agreement" , and did not put MVI into commercial projects, so I gave up talking on paper .

Half a year ago, I finally had the opportunity to practice in commercial projects. I would like to talk with you about my personal , and use this to explain MVI and other architectures.

All content will unfold according to the following points:

  • Starting from the concept of architecture - simply list variousMVX concepts , MVX: refers to MVC, MVP, MVVM, MVI
  • Embracing complexity while achieving simplification-- by comparing and understanding the pain points solved by one-way data flow, the reasons for designing Intent, etc.
  • A single source of trusted data, no rigid beliefs
  • To be elegant, you need tools - with the help of declarative and reactive programming tools, build , shield the details of imperative programming, and also focus and simplify
  • Separation of states and events is not enough

The content will be very long, I will write some more as appropriate , combining examples and code to demonstrate the content.

The basic situation of the two projects

Compared with the previous giant projects, the business volume of these two projects is not large . One is a control APP based on Bluetooth and LAN, referred to as APP-A in the afternoon, and the other is a tool used internally to analyze the logs of various products of the company. Referred to as APP-B.

Although their business depth is deeper than that of ordinary APPs, they are essentially the same . After all, no matter how much the same type of business is, it is just to reuse a set of models, which does not affect the essence.

Consistent with the essence of many projects , they all conform to the logical layering shown in the following figure, and execute business logic in the process of human-computer interaction:

img.png

  • APP-A is an Android project, which is convenient and pure kotlin
  • APP-B is a Compose-Desktop project and has to be kotlin

Too much rambling, let's get to the main text.

Starting from the concept of architecture

Bear in mind that in practice, MVI and MVVM architectures were first proposed by the web application field to solve problems in the development of browser web applications.

In the subsequent development process of application fields, there are common problems, so these designs are introduced and expanded according to their own characteristics.

Next, let's talk about ideas, not martial arts.

idea.jpg

The picture comes from the film generation master

MVI's philosophy

MVIBorn out ofModel View Intent

  • Intent: the intention to drive the model to change, the most common events in the UI;
  • Model: business model, including data and logic, is 客观实体corresponding 程序建模;
  • View: The view of the presentation layer, presenting the state (and events) of the Model in UI, accepting user input, and converting to UI events

The official picture shows the driving relationship between the three very well:

gray-1.png

This picture is very simple, it discards the details of the driving method, and only reflects the relationship between the role and the driving.

Note that as long as the design 角色和驱动关系meets the above figure, it is the MVI architecture design and does not limit驱动方式的实现细节

The details of the classic MVI driver are much more complicated than the above picture, and we will talk about it later.

Starting from the principles of software design: 职责分离并封装the purpose is 解耦, 可独立变化, 复用.

显然,区别于 MVVMMVPMVC,角色上的差别在于 ViewModel、Presenter、Controller、Intent四者,而它们又是View和Model之间的纽带。除此之外,V和M亦稍有不同。

MVC、MVP

MVC、MVP 中,C和P的职责体现为 控制、调度

MVP中 VM 完全解耦可独立变化,MVC中 M 直接操作 V 耦合高,在web应用中,C 需要直接操作DOM。

MVVM

MVVM中,提倡 数据驱动数据源 被剥离到 VM 中,在 双向绑定框架 的加持下,View层的输入反映为数据的变化,数据的变化驱动视图内容。

显然,VM的职责限于维护数据状态,如有必要,驱动View层消费数据状态, 不必再关注如何操作视图。

一般来说,双向绑定框架已经引入观察者模式实现,可响应式驱动,VM一般没有必要关心 响应式驱动和下游观察者生命周期问题

简单思考之后会发现MVVM的问题,它的侧重点在于 利用双向绑定让开发者专注于数据状态的维护,从操作视图更新中得以解放,它难以解决 无天然状态 问题,例如:按钮点击这类事件。

MVI

在MVI中,结合业务背景将UI事件等内容转换为 Intent ,驱动Model层业务,Model层的业务结果反映为 视图状态 + 事件

因此View层和Model层之间已经解耦,并可以吸收MVVM中的优点采用如下设计:

  • 将双向绑定退化为单向绑定,View层消费UI状态流和事件流,这也意味着UI状态的职责精简,它不再承载View层的用户输入等事件
  • 将UI状态独立,Model层仅产生 UI状态的局部变化事件

下图为经典的MVI原理示意图:

gray-2.png

在上文中,我们已经讨论了各个角色的职责,下面逐步展开讨论角色具备的特性和细节知识。

在此之前,还请谨记:合适的才是最好的

没有绝对的最好的设计,只有最合适的设计。

再好的架构,都需要遵循其理念并结合项目因地制宜地进行调整,以获得最佳使用效果。所以请读者诸君务必在阅读时,结合自身项目的情况仔细思考以下问题:

  • 引入新框架所解决的痛点、衍生的问题、是否需要进行框架调整?
  • 框架中的角色功能,为什么出现,又有怎样的局限?

单向数据流动

MVI拥抱了结构复杂,但能够灵活应对业务编码时的各种情况,按部就班即可。

从MVI原理图中,可以清晰的看到 "数据" 的流动方向。 起始于 Intent,经过分类和选择性消费后产生 Result,对应的reducer函数计算后,得到最新的 State (以及裂变出必要的 Event,图中未体现) ,驱动视图。

注意:

  • 单向 是指 单一方向
  • 此处的 数据 是广义的、宽泛的。
  • 仅描述数据流的 变化方向 ,与数据流的数量无关,但一般 形成有效工作 均需要两条数据流(上行数据流和下行数据流)

即驱动数据流变化的方向是唯一的,在英文中的术语为:Unidirectional Data Flow 简称 UDF

MVC、MVP中的痛点

前文我们提到,在MVC和MVP中,着眼于 控制、调度 ,并不强调 数据流 的概念。

View和Model间之间的交互,一般有两种编码风格:双向的API调用、单向的API调用+回调:

注意:以下两图并未体现Controller和Presenter细节,仅表意,从View层出发的API调用和回到View层的UI更新

flow-mvc.png

双向API调用如上图。

flow-mvc2.png

单向API调用+回调更新UI如上图。

显而易见,这两种方式无法继续抽象,需根据实际业务进行命令式编码。当UI复杂时,难以写出清晰、易读的代码,维护难度激增。

MVVM解决UI更新代码混乱问题

前文我们已经提到:MVVM中通过绑定框架,将UI事件转化为数据变化,驱动业务;业务结果表现为数据变化,驱动UI更新。

flow-mvvm.png

显而易见,维护朴素的数据要比直接维护复杂的UI要简单

但问题也同时产生,data1的变化有两个可能的原因:

  • Model层业务结果使其变化,并期望它驱动UI更新
  • View层发生事件,反馈数据变化,并期望它驱动Model层逻辑

因此,框架需要考虑标识数据变化来源、或者其他手段消除方向性所带来的问题。

并且MVVM难以灵活决定的 "何时调用Model层逻辑",即大多数业务中,都需要结合多个属性的变化形成组合条件来驱动Model层逻辑。

本篇并不重点讨论MVVM,故不再展开MVVM解决循环更新的方案,以及衍生的问题。

尽管如此,MVVM中的数据绑定依旧解决了View层更新繁杂的问题。

用Intent灵活决定何时调用Model

既然数据驱动UI有极大的益处,且View层事件驱动ViewModel的数据变化有很多弊端 (需要建立很高的复杂度) ,那自然需要 趋利避害

flow-mvi.png

仅保留数据驱动UI的部分,并增加Intent用以驱动Model层业务

在于 MVC/MVP 以及 MVVM 对比后不难得出结论:

  • MVC/MVP中,View层通过调用C/P层API的方式最终调用到Model层业务,方式质朴、无难度。但业务量规模增大后接口方法数也会增多,导致C/P层尾大不掉,难以重用。
  • MVVM中,VM层总是需要利用 技巧 进行模型概念转换,以满足业务响应满足实际需求,需要很深厚的设计经验才能写出非常优秀的代码,这并不友好。

作者按:我个人认为一个友好的设计,不应当剑走偏锋,而应当大巧不工,能够以力破法,达成 "使用者只需要吃透理论就可以解决各类问题" 的目标。

而MVI在架构角色中设计了Intent的角色:

  • 它包含了业务调用的意图和数据
  • 从设计上可满足 调用实现 的分离
  • 架构模型中以Intent流的形式出现,下游对其的 筛选转换消费 等行为可遵循 FP范式 (即函数式编程范式、Functional Programming Patterns) ,逻辑的复用粒度为方法级,复用度更高更灵活
  • 解决了MVVM中的方向性问题、MVC/MVP 中的灵活度问题等

单一可信数据源

我猜测读者诸君都曾听过这个词,将 单一可信数据源 拆解一下:

  • 单一
  • 可信
  • 数据源

在MVI背景下,数据源 指的是视图对应的数据实体,它代表视图的内容状态。

可信指从数据源中获取的数据是 最新的完整的可靠的,否则是不可信的,我们没有理由在编码中使用不可信的数据源

单一是指这样的数据源仅一个。

在经典设计中,其内涵如下图:

mvi-ssot.png

  • 按照视图的 所有的 内容状态,定义一个不可变的 ViewState
  • 按照业务初始化 ViewState 实例
  • Model业务生成驱动 ViewState变化的Result
  • 计算出新状态,Reduce(Pre-ViewState,Result) -> New-ViewState
  • 更新数据源
  • View层消费ViewState

借助于数据绑定框架,可以很方便地解决视图更新的问题。

想象一下,此时页面UI非常复杂……

img.png

如果僵化的信奉这样的 单一 ,情况会如何呢?

  • 复杂(大量属性)的ViewState
  • 复杂的UI更新计算,e.g. 100个属性变了2个,依然需要计算98个属性未变或者全量强制更新

在 APP-A和APP-B中,我分别使用了 DataBinding和Compose,但均无法避免该问题。

何为单一

从机器执行程序的原理上看,我们无法实现 多个内容一致的数据源任意时刻 满足 最新的可靠的

将视图视为一个整体,规定它只拥有 一个 可信的数据源。在此基础上看局部的视图,它们也顺其自然地仅拥有一个可信的数据源。

反过来看,当任意的局部视图仅具有一个可信数据源时,整体视图也仅有一个逻辑上的可信数据源。

据此,我们可以对 经典MVI实现 进行一定程度的改造,将ViewState进行局部分解,使得UI绑定部分的业务逻辑更 清晰、干净

请注意,复杂度不会凭空消失,我们为了让 "UI绑定的业务逻辑更清晰、干净"、"更新UI的计算量更少",将复杂度转移到了ViewState的拆分。拆分后,将具有 多个视图部件的单一可信数据源,注意,为了不引起额外的麻烦、并且便于维护扩展,建议遵守以下条件:

  • 基于业务需求,组合数据源形成新数据源
  • 不在数据源的逻辑范围之外进行数据源组合操作

举个虚拟的例子:用户需要实名认证 且 关注博主 ,才在界面上显示某功能按钮。下面使用代码分别演示。

考虑到RxJava的广泛度依旧高于Kotlin-Coroutine+flow,数据流的实现采用RxJava

注意,考虑到读者可能会编写demo做UDF局部的验证,下文中的代码以示例目的为主,兼顾编写场景冒烟的方便性,流的类型不一定是构建完整UDF的最佳选择。

经典实现

在经典MVI实现中,需要先定义ViewState

data class ViewState(
    /*unique id of current login user*/
    val userId: Int,
    /*true if the current login user has complete real-name verified*/
    val realNameVerified: Boolean,
    /*true if the current login user has followed the author*/
    val hasFollowAuthor: Boolean
) {
}
复制代码

并定义ViewModel,创建ViewState流,忽略掉其初始化和其他部分

class VM {
    val viewState = BehaviorSubject.create<ViewState>()

    //ignore
}
复制代码

并定义View层,忽略掉其他部分,简单起见暂时不使用数据绑定框架

class View {

    private val vm = VM()
    lateinit var imgRealNameVerified: ImageView
    lateinit var cbHasFollowAuthor: CheckBox
    lateinit var someButton: Button

    fun onCreate() {
        //ignore view initialize

        vm.viewState.subscribe {
            render(it)
        }
    }

    private fun render(state: ViewState) {
        imgRealNameVerified.isVisible = state.realNameVerified
        cbHasFollowAuthor.isChecked = state.hasFollowAuthor
        someButton.isVisible = state.realNameVerified && state.hasFollowAuthor

        //ignore other
    }
}
复制代码

在JS中,JSON并不能附加逻辑,基本等价于Java中的POJO,故在数据源外部处理简单逻辑的情况较为常见。而在Java、Kotlin中可以进行适当的优化,适当封装,使得代码更加干净便于维护:

data class ViewState(
    //ignore
) {
    fun isSomeFuncEnabled():Boolean = realNameVerified && hasFollowAuthor
}

class View {
    //ignore
    
    private fun render(state: ViewState) {
        //...
        
        someButton.isVisible = state.isSomeFuncEnabled()
    }
}
复制代码

拆分实现

依旧先定义逻辑上完整的ViewState:

class ComposedViewState(
    /*unique id of current login user*/
    val userId: Int,
) {

    /**
     * real-name-verified observable subject,feed true if the current login user has complete real-name verified
     * */
    val realNameVerified = BehaviorSubject.create<Boolean>()

    /**
     * follow-author observable subject, feed true if the current login user has followed the author
     * */
    val hasFollowAuthor = BehaviorSubject.create<Boolean>()

    val someFuncEnabled = BehaviorSubject.combineLatest(realNameVerified, hasFollowAuthor) { a, b -> a && b }
}
复制代码

定义ViewModel,子模块数据流均已定义,故而无需再定义全ViewState的流

class VM(val userId: Int) {
    val viewState = ComposedViewState(userId)
    //ignore
}
复制代码

编写View层的UI绑定,同样简单起见,不使用数据绑定框架

class View {

    private val vm = VM(1)
    lateinit var imgRealNameVerified: ImageView
    lateinit var cbHasFollowAuthor: CheckBox
    lateinit var someButton: Button

    fun onCreate() {
        //ignore view initialize
        bindViewStateWithUI()
    }

    private fun bindViewStateWithUI() {
        vm.viewState.realNameVerified.subscribe {
            renderSection1(it)
        }

        vm.viewState.hasFollowAuthor.subscribe {
            renderSection2(it)
        }

        vm.viewState.someFuncEnabled.subscribe {
            renderSection3(it)
        }
        //...
    }

    private fun renderSection1(foo:Boolean) {
        imgRealNameVerified.isVisible = foo
    }

    private fun renderSection2(foo:Boolean) {
        cbHasFollowAuthor.isChecked = foo
    }

    private fun renderSection3(foo:Boolean) {
        someButton.isVisible = foo
    }
}
复制代码

例子较为简单,在实际项目中,如果遇到复杂页面,则可以分块进行处理。

注意:实际情况中,并没有必要将每一个子数据源拆分到一个View级别的控件,那样过于啰嗦,例子因非常简单而无法丰满起来。 e.g. 针对每一块视图区,例如作者区域,定义子ViewState类,创建其数据流即可。

作者按:务必评估,在一次Model业务产生的Result中,会引起数据流下游的更新次数。 为避免产生不可预期的问题,可通过类似以下方式,使下游响应次数表现和经典实现的情况一致。

额外定义PartialChange流或者功能等价的流,它用于标识 reduce 计算的开始和结束,可以将此期间的数据流的变化延迟到最后发送终态

更加推荐定义功能上等价的流

class ComposedViewState(
    /*unique id of current login user*/
    val userId: Int,
) {

    internal val changes = BehaviorSubject.create<PartialChange>()
    
    //ignore

    val someFuncEnabled =
        BehaviorSubject.combineLatest(realNameVerified, hasFollowAuthor) { a, b -> a && b }.sync(PartialChange.Tag, changes)
}

inline fun <reified T, S> Observable<T>.sync(tag: S, sync: BehaviorSubject<S>): Observable<T> {
    return BehaviorSubject.combineLatest(this, sync) { source, syncItem ->
        if (syncItem == tag) {
            syncItem
        } else {
            source
        }
    }.filter { it is T }.cast(T::class.java)
}
复制代码

修改PartialChange,为reduce函数添加边界:

PartialChange是Model产生的Result的表现物,封装了ViewState的reduce函数逻辑,即如何从 Pre-ViewState 生成 新 ViewState

sealed class PartialChange {
    open fun reduce(state: ComposedViewState) {

    }

    /**
     * 同步标记,从头开始到真实PartialChange之间,流的状态生效
     * */
    object Tag : PartialChange()

    object None : PartialChange()

    class Foo(val a: Boolean, val b: Boolean) : PartialChange() {
        override fun reduce(state: ComposedViewState) {
            state.changes.onNext(Tag)
            state.realNameVerified.onNext(a)
            state.hasFollowAuthor.onNext(b)
            state.changes.onNext(this)
        }
    }
}
复制代码

要想优雅,需要工具

采用响应式流,避免命令式编码

想来这一点已不需要多做解释。

在Android中,存在 LiveData 组件,它通过简单的方式封装了可观测的数据,但实现方式简单也限制了它的功能 不够强大 。因此,建议使用 RxJava 或者 Kotlin-Coroutine & flow 构建数据流。

本节便不再展开。

采用数据绑定框架

采用 jetpack-compose 或者 DataBinding 均可以移除枯燥的UI命令式逻辑,在APP-A中我使用了DataBinding,在APP-B中我使用了Compose。

在 ViewState的代码很棒时,均可以获得优秀的编程体验,从啰嗦的UI中解放出来。

作者的个人观点:

关于Compose。Compose依旧属于较新的事物,在商业项目中使用存在学习门槛和造轮工作。在目标用户具有较高容忍度的情况下,已然可以进行尝试。

关于DataBinding。一个近乎毁誉参半的工具,关于它的批判,大多集中于:xml中实现的逻辑难以阅读、维护,这实际上是对DataBinding设计的误解而带来的错误使用。

DataBinding本身具有生成VM层的功能,但这一功能并不足够强大,且没有完善的使用指导,而在官方Demo中过度宣传了它,导致大家认为DataBinding就该这样使用。

仅使用基础的数据绑定功能、和Resource或者Context有关的功能(例如字符串模板)、组件生命周期绑定等,适度自定义绑定。

何为状态、何为事件。最后的一公里

首先区别于上文提到的UI事件,这里的状态和事件均产生于数据流的末段,而UI事件处于数据流的首段。

UI事件属于:A possible action that the user can perform that is monitored by an application or the operating system (event listener). When an event occurs an event handler is called which performs a specific task

在展开之前,先用一张图回顾总结上文中对于 单向数据流 & 单一可信数据源 的知识

MVI-UDF.png

单向数据流动 章节中,提到了MVI的UDF设计:

  • 系统捕获的UI事件、其他侦听事件(例如熄屏、应用生命周期事件),生成Intent,压入Intent流中
  • ViewModel层中筛选、转换、处理Intent,实际是使用Model层业务,产生业务结果,即PartialChange
  • PartialChange经过Reducer计算处理得到最新的ViewState,压入ViewState流
  • View层(广义的表现层)响应并呈现最新的ViewState

单一可信数据源 章节中,提到View层应当采用 单一可信数据源

在这张图中,我们仅体现了 状态 即 ViewState。

关于GUI程序的认知

在展开前,先聊点理念上的内容。请读者诸君思考下自己对于GUI程序的认知。

作者的理解:

程序狭义上是计算机能识别和执行的一组指令集,编程工作是在程序世界对 客观实体业务逻辑 进行 建模和逻辑表达。

而GUI程序拥有 用户图形界面 , 除了结合硬件接收用户交互输入外,可以将 程序世界中的模型用户图形界面 等方式表现给用户。

表现出来的内容代表着客观实体

其本质目的在于:通过 描述特征属性描述变化过程 等方式让用户感知并理解 客观实体

而除了通过 程序语言描述程序世界模拟展现 外,同样可以通过 自然语言描述 达到目的,这也是产品经理的工作。

当然,产品经理往往需要借助一些工具来提升自己的自然语言表达能力,但无奈的是能用数学公式和逻辑推演表达需求的产品经理太少见了。

写这段只是为了引入 他山之石

First-Order logic

在数学、哲学、语言学、计算机科学中,有一个概念 First-Order logic,无论是产品需求还是计算机程序,都可以建立FOL表达

当然,本篇不讨论FOL,那是一个很庞大且偏离主题的事情。我仅仅是想借用其中的概念。

FOL表达 Event或者State时:

  • Event 体现的是特定的变化
  • State 体现的是客观实体在任意时刻都适用的一组情况,即一段时间内无变化的条件或者特征

不难理解,变化是瞬时的,连续的变化是可分的。

但在人机交互中,瞬时意义很小,我们的目的在于让用户感知。

例如:"好友向你发送了一条消息的场景中",消息抵达就是Event,它背后潜藏着 "消息数的变化"、"最新消息内容的变化" 等。 在常见的设计中:

  • 应用需要弹出一个气泡通知用户这一事件
  • 应用需要更新消息数,消息列表内容等,以呈现出最新的State

而为了让用户感知到,气泡呈现时长并不是瞬时的,但在产品交互设计中依旧将其定义为事件。

分离状态和事件,不是吃饱撑得

看山是山、看水是水

此时此刻,答案已经很明显。

在通用的产品设计中,状态和事件有不同的意义,如果程序中不分离出两者,则必然是自找麻烦,这是公然挑衅 面向对象编程 的行为。如果不明确定义不同的Class,则势必导致代码混乱不堪,毕竟这是违背编程原则的事情。

在大多MVVM设计中,状态和事件未分家,导致bug丛生,这一点便不再展开。

如何区分Event和State

State是一段时间内无变化的条件或者特征,它天然的 契合 了位于表现层的主体内容所对应的 数据模型特征

Event是特定的变化,它在表现层体现,但与State的生命周期不一致,且并无一一对应的关系。

基于经验主义,我们可以机械地、笼统地认为:页面主体静态内容所需要的数据属于State范畴,气泡提醒等短暂的物体所需要的数据属于Event范畴。

从逻辑推演的角度出发,进行 等价逻辑推断条件限定下的逻辑推断 ,一定序列的Event可以模型转换为State。

事件粘性导致重复?只是框架设计的bug

看山不是山,看水不是水

前面提到,State是一段时间内无变化的条件或者特征,所以在程序设计中State具有粘性的特征。

如果Event也设计出这样的粘性特征并造成重复消费,明显是违背需求的,无疑是框架设计的Bug。此问题在各大论坛中很常见。

注意,我们无法脱离实际需求去二元化的讨论事件本身该不该有粘性特征,只能结合实际讨论框架功能是否存在bug

如果要实现以力破法,在框架设计层面上 Event体系的设计要比State体系要复杂 。因为从交互设计上:

  • State 只需要考虑呈现的准确性和及时性,除去美观、可理解性等等
  • Event 需要考虑准确性、优先级、及时性、按条件丢弃等等,除去美观、可理解性等等

举个例子:网络连接问题导致的Web-API调用失败需要使用Toast提示网络连接失败

不难想象:

  • 可能一瞬间的断开网络连接,会导致多个连接均返回失败
  • 可能连接问题未修复,10秒前请求失败,当前请求又失败了

难道连续弹出吗?难道和上一次Event一致就不消费吗?...

或许您会使用一些 剑走偏锋的技巧 来解决问题,但技巧总是建立在特定条件下生效的,一旦条件发生变化,就会带来烦恼,您很难控制上游的PM和交互设计师。

所以在框架层面需要针对产品、交互设计的泛化理念,设计准确的、灵活的Event体系。

准确的、灵活的Event体系

看山还是山,看水还是水

回到FOL中,为了更加准确的表达Event和State的含义,还需要一些额外的参数,例如:参与者地点时间 等。

想通这一点会发现,产品中定义的Event事件、及其消费逻辑均含有隐藏属性,例如:

  • 发生时间
  • 客观有效期
  • 判断有效的条件(如呈现的条件)
  • 判断失效的条件 ,用于实现提前失效

产品经理和交互设计师一般会使用 "响应时间"、"优先级" 等词描述它们,但一般不严谨、不成体系,带来期望不一致的问题

In contrast, the State flow represents the complete change of the main content of the interface on the time axis, and the conditions and characteristics corresponding to the interface content can be obtained at any point in time. Once a new state appears in the state flow, it is reflected in the presentation layer in a timely and accurate manner.

It is not difficult to understand that the life cycle of a State is 从init或者reducer计算生成开始to reducer计算出新State、宿主生命期结束为止, which is already implied in the State flow:

  • No lifecycle overlap between States
  • The sum of the lifetimes of all states fills the timeline

As mentioned above, Event is instantaneous, so Event itself does not have a substantial life cycle. For the convenience of expression, we define "Event from generation to unobservable stage at the presentation layer" as the Event life cycle

The Event stream is different from the State stream, because the life cycle of the Event is more complicated:

  • Events may have overlapping lifecycles
  • The sum of the lifetimes of all Events may not cover the complete timeline

Additional design implementation is required . After this is achieved, the sub- will have the same properties .

At this moment, you will find that according to the interaction characteristics corresponding to different types of event interaction controls, the Event flow is combined with the conditional flow to derive each State flow. The full data flow details are as follows:

mvi-udf2.png

Author's note: The process of event shunting into sub-state flow is omitted in the figure, because it needs to follow a specific product interaction mechanism

Epilogue

This article, from the plan to write in May to the start of writing in June, was intermittently written. The draft was written for a long time. After several deletions and revisions, there is still a long space left. Although I have tried my best to be wise, I feel that the words are not clear. Welcome to discuss in the comment area.

Guess you like

Origin juejin.im/post/7135328592673636359