响应式架构最佳实践——MVI
点击上⽅蓝字关注我,知识会给你⼒量
这个系列我做了协程和Flow开发者的⼀系列⽂章的翻译,旨在了解当前协程、Flow、LiveData这样设计的原因,从设计者的⾓度,发现他们的问题,以及如何解决这些问题,pls enjoy it。
MVVM和MVI架构模式的精华合⼆为⼀,为任何Android项⽬提供了完美的架构。
如果你已经知道架构模式的基本原则,以及MVVM和MVI模式的细节,那么跳过基础知识,跳到⽂章的
MVI+LiveData+ViewModel(或第⼆部分)。
Preface
有这么多的架构模式,每个模式都有⼀些优点和缺点。所有这些模式都试图实现相同的架构基本原则。
1.
Separation of concerns(SoC)。这是⼀个将计算机程序分离成不同部分的设计原则,使每个部分解决⼀个单独的问题。关注点是指在提供问题的解决⽅案⽅⾯的任何事情。这⼀原则与⾯向对象编程的单⼀责任原则密切相关,该原则指出:"每个模块、类或函数都应该对软件所提供的功能的单⼀部分负责,⽽且该责任应该完全由类、模块或函数封装。" -
2.
Drive UI from a model。应⽤程序应该从⼀个Model中驱动⽤户界⾯,最好是⼀个持久性Model。Model独⽴于视图对象和应⽤程序组件,所以它们不受应⽤程序的⽣命周期和相关关注点的影响。
让我们也来看看⼀些流⾏的架构模式的总结。
MVC Architecture:
Trygve Reenskaug的Model-视图-控制器架构是所有现代架构模式的基础。让我们来看看上定义的每个组件的职责。
Model负责管理应⽤程序的数据。它接收来⾃controller的输⼊。
View意味着以特定的格式展⽰Model。
controller对⽤户的输⼊做出反应,并对数据Model对象进⾏交互。controller接收输⼊,选择性地验证它,然后将输⼊传递给Model。所以,Model负责表⽰状态、结构和视图的⾏为,⽽视图只不过是该Model的代表。
MVVM Architecture:
在Model-View-ViewModel架构中,视图拥有ViewModel的实例,它根据⽤户的输⼊/动作调⽤相应的函数。同时,视图观察ViewModel的不同可观察属性的变化。ViewModel根据业务逻辑处理⽤户输⼊并修改各⾃的可观察属性。
MVI Architecture:
在Model-View-Intent架构中,视图暴露了视图-事件(⽤户输⼊/⾏动),并观察Model的视图状态变化。我们处理视图事件,将其转换为各⾃的意图,并将其传递给Model。Model层使⽤意图和先前的视图状态创建⼀个新的不可变的视图
事件,将其转换为各⾃的意图,并将其传递给Model。Model层使⽤意图和先前的视图状态创建⼀个新的不可变的视图状态。因此,这种⽅式遵循单向数据流原则,即数据只在⼀个⽅向流动。View>Intent>Model>View。
总之,MVVM架构最好的部分是ViewModel,但我认为它没有遵循MVC模式中定义的Model概念,因为在MVVM
中,DAO(数据访问对象)的抽象被认为是Model,视图观察来⾃ViewModel的多个可观察属性的状态变化,视图不是由Model直接驱动。另外,这些来⾃ViewModel的多个可观察属性会导致状态重叠问题(两个不同的状态被意外显⽰)。
MVI模式通过添加⼀个实际的 "Model "层来解决这个问题,该层由视图观察状态变化。由于这个Model是不可改变的,并且是当前视图状态的单⼀真理来源,所以状态重叠不会发⽣。
在下⾯的架构中,我试图结合MVVM和MVI模式的优点,为任何Android项⽬提供更好的架构,在此基础上,我通过为View和ViewModel创建基类,尽可能多地抽象出⼀些东西。
MVI + LiveData + ViewModel = ❤  Architecture:
在继续之前,让我们重新强调⼀下MVI架构中的⼀些基本术语。
ViewState:顾名思义,这是Model层的⼀部分,我们的视图要观察这个Model的状态变化。ViewState应该代表视图在任何给定时间的当前状态。所以这个类应该有我们的视图所依赖的所有变量内容。每次有任何⽤户的输⼊/动作,我们都会暴露这个类的修改过的副本(以保持之前没有被修改的状态)。我们可以使⽤Kotlin的Data Class来创建这个Model。data class MainViewState(val fetchStatus: FetchStatus, val newsList: List<NewsItem>)
sealed class FetchStatus {
object Fetching : FetchStatus
object Fetched : FetchStatus
object NotFetched : FetchStatus
}
ViewEffect:在Android中,我们有⼀些动作更像是fire-and-forget,例如Toast,在这些情况下,我们不能使⽤ViewState,因为它保持状态。这意味着,如果我们使⽤ViewState来显⽰Toast,它将在配置改变或每次有新的状态时再次显⽰,除⾮我们通过 "toast is shown "事件来重置其状态。如果你不希望这样做,你可以使⽤ViewEffect,因为它是基于SingleLiveEvent的,不需要维护状态。ViewEffect也是
我们Model的⼀部分,我们可以使⽤Kotlin的密封类来创建它。
sealed class MainViewEffect {
data class ShowSnackbar(val message: String) : MainViewEffect
data class ShowToast(val message: String) : MainViewEffect
}
ViewEvent:它表⽰⽤户可以在视图上执⾏的所有动作/事件。它⽤于将⽤户的输⼊/动作传递给ViewModel。我们可以使⽤Kotlin的Sealed Class来创建这个事件集。
sealed class MainViewEvent {
data class NewsItemClicked(val newsItem: NewsItem) : MainViewEvent
object FabClicked : MainViewEvent
object OnSwipeRefresh : MainViewEvent
object FetchNews : MainViewEvent
}
我建议你,把这三个类放在⼀个⽂件⾥,因为它能让你对⽬标视图处理的所有可做动作和变量内容有⼀个整体的概念。现在,让我们更深⼊地了解这个架构。
img
上⾯的图可能已经给了你这个架构的核⼼思想。如果没有,这个架构的核⼼思想是,我们在MVVM架构中包括⼀个实际的不可变的Model层,我们的视图依赖于这个Model的状态变化。这样⼀来,ViewModel就必须修改和公开这个单⼀的Model。
为了避免冗余和简化这种架构在多个地⽅的使⽤,我创建了两个抽象类,⼀个⽤于我们的视图(为Activity、Fragment、⾃定义视图分开),⼀个⽤于ViewModel。
AacMviViewModel。⼀个通⽤的基类来创建ViewModel。它需要三个类STATE、EFFECT和EVENT。我们已经在上⾯看到了这些类的⼀个例⼦。
看到了这些类的⼀个例⼦。
open class AacMviViewModel<STATE, EFFECT, EVENT>(application: Application) : AndroidViewModel(application), ViewModelContract<EVENT> {
private val _viewStates: MutableLiveData<STATE> = MutableLiveData
fun viewStates: LiveData<STATE> = _viewStates
private var _viewState: STATE? = null
protected var viewState: STATE
get = _viewState
: throw UninitializedPropertyAccessException( "\"viewState\" was queried before being initialized") set(value) {
Log.d(TAG, "setting viewState : $value" )
_viewState = value
_viewStates.value = value
}
private val _viewEffects: SingleLiveEvent<EFFECT> = SingleLiveEvent
fun viewEffects: SingleLiveEvent<EFFECT> = _viewEffects
private var _viewEffect: EFFECT? = null
protected var viewEffect: EFFECT
get = _viewEffect
: throw UninitializedPropertyAccessException( "\"viewEffect\" was queried before being initialized") set(value) {
Log.d(TAG, "setting viewEffect : $value" )
_viewEffect = value
_viewEffects.value = value
}
@CallSuper
override fun process(viewEvent: EVENT) {
Log.d(TAG, "processing viewEvent: $viewEvent" )
}
}
如你所见,我们有viewState。STATE和viewEffect。EFFECT和两个私有的LiveData容器_viewStates。MutableLiveData 和_viewEffect: SingleLiveEvent ,它们通过公共函数viewStates和viewEffects被暴露出来。请注意,我们正在扩展AndroidViewModel,因为它将允许我们在需要时使⽤应⽤程序上下⽂(仅)。此外,我们正在记录每个viewEvent,我们将处理这些事件。
这就是我们如何为我们的任何Activity/Fragment/视图创建⼀个ViewModel。
class MainActVM(application: Application) :
AacMviViewModel<MainViewState, MainViewEffect, MainViewEvent>(application) {
init {
viewState = MainViewState(fetchStatus = FetchStatus.NotFetched, newsList = emptyList)
}
override fun process(viewEvent: MainViewEvent) {
super.process(viewEvent)
}
}
我们所要做的就是在init{}块中初始化viewState,并在需要时使⽤数据类的copy函数进⼀步修改viewState。请不要修改viewState的同⼀个实例,⽽要修改它的副本,这样我们就能保持viewState的不可改变性。如果你修改了viewState的同⼀个实例,你可能会遇到意外的⾏为,因为你可能会改变⼀些正在被视图处理的属性。
viewState = py(fetchStatus = FetchStatus.Fetched, newsList = result.data)
就是这样,其余部分(即_viewStates.setValue)由AacMviViewModel类处理。
AacMviActivity。⼀个通⽤的抽象类,⽤于为这个架构创建兼容的Activity。
abstract class AacMviActivity<STATE, EFFECT, EVENT, ViewModel : AacMviViewModel<STATE, EFFECT, EVENT>> :
AppCompatActivity{
fetch最佳用法
abstract val viewModel: ViewModel
private val viewStateObserver = Observer<STATE> {
Log.d(TAG, "observed viewState : $it" )
renderViewState(it)

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。