• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

MVVM-成为历史Google-全面倒向-MVI

android 搞代码 3年前 (2022-03-01) 20次浏览 已收录 0个评论
文章目录[隐藏]

前言

前段时间写了一些介绍MVI架构的文章,不过软件开发上没有最好的架构,只有最合适的架构,同时家喻户晓,Google举荐的是MVVM架构。置信很多人都会有疑难,我为什么不应用官网举荐的MVVM,而要用你说的这个什么MVI架构呢?

不过我这几天查看Android的利用架构指南,发现谷歌举荐的最佳实际曾经变成了单向数据流动 + 状态集中管理,这不就是MVI架构吗?看起来Google曾经开始举荐应用MVI架构了,大家也有必要开始理解一下Android利用架构指南的最新版本了~

总体架构

两个架构准则

Android的架构设计准则次要有两个

拆散关注点

要遵循的最重要的准则是拆散关注点。一种常见的谬误是在一个 ActivityFragment 中编写所有代码。这些基于界面的类应仅蕴含解决界面和操作系统交互的逻辑。总得来说,ActivityFragment中的代码应该尽量精简,尽量将业务逻辑迁徙到其它层

通过数据驱动界面

另一个重要准则是您应该通过数据驱动界面(最好是持久性模型)。数据模型独立于利用中的界面元素和其余组件。
这意味着它们与界面和利用组件的生命周期没有关联,但仍会在操作系统决定从内存中移除利用的过程时被销毁。
数据模型与界面元素,生命周期解耦,因而不便复用,同时便于测试,更加稳固牢靠。

举荐的利用架构

基于上一部分提到的常见架构准则,每个利用应至多有两个层:

  • 界面层 – 在屏幕上显示利用数据。
  • 数据层 – 提供所须要的利用数据。

您能够额定增加一个名为“网域层”的架构层,以简化和复用应用界面层与数据层之间的交互

如上所示,各层之间的依赖关系是单向依赖的,网域层,数据层不依赖于界面层

界面层

界面的作用是在屏幕上显示利用数据,并响应用户的点击。每当数据发生变化时,无论是因为用户互动(例如按了某个按钮),还是因为内部输出(例如网络响应),界面都应随之更新,以反映这些变动。
不过,从数据层获取的利用数据的格局通常不同于UI须要展现的数据的格局,因而咱们须要将数据层数据转化为页面的状态
因而界面层个别分为两局部,即UI层与State HolderState Holder的角色个别由ViewModel承当

数据层的作用是存储和治理利用数据,以及提供对利用数据的拜访权限,因而界面层必须执行以下步骤:

  1. 获取利用数据,并将其转换为UI能够轻松出现的UI State
  2. 订阅UI State,当页面状态产生扭转时刷新UI
  3. 接管用户的输出事件,并依据相应的事件进行解决,从而刷新UI State
  4. 依据须要反复第 1-3 步。

次要是一个单向数据流动,如下图所示:

因而界面层次要须要做以下工作:

  1. 如何定义UI State
  2. 如何应用单向数据流 (UDF),作为提供和治理UI State的形式。
  3. 如何裸露与更新UI State
  4. 如何订阅UI State

如何定义UI State

如果咱们要实现一个新闻列表界面,咱们该怎么定义UI State呢?咱们将界面须要的所有状态都封装在一个data class中。
与之前的MVVM模式的次要区别之一也在这里,即之前通常是一个State对应一个LiveData,而MVI架构则强调对UI State的集中管理

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf(),
    val userMessages: List<Message> = listOf()
)

data class NewsItemUiState(
    val title: String,
    val body: String,
    val bookmarked: Boolean = false,
    ...
)

以上示例中的UI State定义是不可变的。这样的次要益处是,不可变对象可保障即时提供利用的状态。这样一来,UI便可专一于施展繁多作用:读取UI State并相应地更新其UI元素。因而,切勿间接在UI中批改UI State。违反这个准则会导致同一条信息有多个可信起源,从而导致数据不统一的问题。

例如,如上中来自UI StateNewsItemUiState对象中的bookmarked标记在Activity类中已更新,那么该标记会与数据层开展竞争,从而产生多数据源的问题。

UI State集中管理的优缺点

MVVM中咱们通常是多个数据流,即一个State对应一个LiveData,而MVI中则是单个数据流。两者各有什么优缺点?
单个数据流的长处次要在于不便,缩小模板代码,增加一个状态只须要给data class增加一个属性即可,能够无效地升高ViewModelView的通信老本
同时UI State集中管理能够轻松地实现相似MediatorLiveData的成果,比方可能只有在用户已登录并且是付费新闻服务订阅者时,您才须要显示书签按钮。您能够按如下形式定义UI State

data class NewsUiState(
    val isSignedIn: Boolean = false,
    val isPremium: Boolean = false,
    val newsItems: List<NewsItemUiState> = listOf()
){
 val canBookmarkNews: Boolean get() = isSignedIn && isPremium
}

如上所示,书签的可见性是其它两个属性的派生属性,其它两个属性发生变化时,canBookmarkNews也会主动变动,当咱们须要实现书签的可见与暗藏逻辑,只须要订阅canBookmarkNews即可,这样能够轻松实现相似MediatorLiveData的成果,然而远比MediatorLiveData要简略

当然,UI State集中管理也会有一些问题:

  • 不相干的数据类型:UI所需的某些状态可能是齐全互相独立的。在此类情况下,将这些不同的状态捆绑在一起的代价可能会超过其劣势,尤其是当其中某个状态的更新频率高于其余状态的更新频率时。
  • UiState diffingUiState 对象中的字段越多,数据流就越有可能因为其中一个字段被更新而收回。因为视图没有 diffing 机制来理解间断收回的数据流是否雷同,因而每次收回都会导致视图更新。当然,咱们能够对 LiveDataFlow应用 distinctUntilChanged() 等办法来实现部分刷新,从而解决这个问题

应用单向数据流治理UI State

上文提到,为了保障UI中不能批改状态,UI State中的元素都是不可变的,那么如何更新UI State呢?
咱们个别应用ViewModel作为UI State的容器,因而响应用户输出更新UI State次要分为以下几步:

  1. ViewModel 会存储并公开UI StateUI State是通过ViewModel转换的利用数据。
  2. UI层会向ViewModel发送用户事件告诉。
  3. ViewModel会解决用户操作并更新UI State
  4. 更新后的状态将反馈给UI以进行出现。
  5. 零碎会对导致状态更改的所有事件反复上述操作。

举个例子,如果用户须要给新闻列表加个书签,那么就须要将事件传递给ViewModel,而后ViewModel更新UI State(两头可能有数据层的更新),UI层订阅UI State订响应刷新,从而实现页面刷新,如下图所示:

为什么应用单向数据流动?

单向数据流动能够实现关注点拆散准则,它能够将状态变动起源地位、转换地位以及最终应用地位进行拆散。
这种拆散可让UI只施展其名称所表明的作用:通过观察UI State变动来显示页面信息,并将用户输出传递给ViewModel以实现状态刷新。

换句话说,单向数据流动有助于实现以下几点:

  1. 数据一致性。界面只有一个可信起源。
  2. 可测试性。状态起源是独立的,因而可独立于界面进行测试。
  3. 可维护性。状态的更改遵循明确定义的模式,即状态更改是用户事件及其数据拉取起源独特作用的后果。

裸露与更新UI State

定义好UI State并确定如何治理相应状态后,下一步是将提供的状态发送给界面。咱们能够应用LiveData或者StateFlowUI State转化为数据流并裸露给UI
为了保障不能在UI中批改状态,咱们应该定义一个可变的StateFlow与一个不可变的StateFlow,如下所示:

class NewsViewModel(...) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    ...

}

这样一来,UI层能够订阅状态,而ViewModel也能够批改状态,以须要执行异步操作的状况为例,能够应用viewModelScope启动协程,并且能够在操作实现时更新状态。

class NewsViewModel(
    private val repository: NewsRepository,
    ...
) : ViewModel() {

    private val _uiState = MutableStateFlow(NewsUiState())
    val uiState: StateFlow<NewsUiState> = _uiState.asStateFlow()

    private var fetchJob: Job? = null

    fun fetchArticles(category: String) {
        fetchJob?.cancel()
        fetchJob = viewModelScope.launch {
            try {
                val newsItems = repository.newsItemsForCategory(category)
                _uiState.update {
                    it.copy(newsItems = newsItems)
                }
            } catch (ioe: IOException) {
                // Handle the error and notify the notify the UI when appropriate.
                _uiState.update {
                    val messages = getMessagesFromThrowable(ioe)
                    it.copy(userMessages = messages)
                 }
            }
        }
    }
}

在下面的示例中,NewsViewModel 类会尝试进行网络申请,而后更新UI State,而后UI层能够对其做出适当反馈

订阅UI State

订阅UI State很简略,只须要在UI层察看并刷新UI即可

class NewsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.uiState.collect {
                    // Update UI elements
                }
            }
        }
    }
}

UI State实现部分刷新

因为MVI架构下实现了UI State的集中管理,因而更新一个属性就会导致UI State的更新,那么在这种状况下怎么实现部分刷新呢?
咱们能够利用distinctUntilChanged实现,distinctUntilChanged只有在值发生变化了之后才会回调刷新,相当于对属性做了一个防抖,因而咱们能够实现部分刷新,应用形式如下所示

class NewsActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                // Bind the visibility of the progressBar to the state
                // of isFetchingArticles.
                viewModel.uiState
                    .map { it.isFetchingArticles }
                    .distinctUntilChanged()
                    .collect { progressBar.isVisible = it }
            }
        }
    }
}

当然咱们也能够对其进行肯定的封装,给Flow或者LiveData增加一个扩大函数,令其反对监听属性即可,应用形式如下所示

class MainActivity : AppCompatActivity() {
 private fun initViewModel() {
        viewModel.viewStates.run {
            //监听newsList
            observeState(this@MainActivity, MainViewState::newsList) {
                newsRvAdapter.submitList(it)
            }
            //监听网络状态
            observeState(this@MainActivity, MainViewState::fetchStatus) {
                //..
            }
        }
    }
}

对于MVI架构下反对属性监听,更加具体地内容可见:MVI 架构更佳实际:反对 LiveData 属性监听

网域层

网域层是位于界面层和数据层之间的可选层。

网域层负责封装简单的业务逻辑,或者由多个ViewModel重复使用的简略业务逻辑。此层是可选的,因为并非所有利用都有这类需要。因而,您应仅在须要时应用该层。
网域层具备以下劣势:

  1. 防止代码反复。
  2. 改善应用网域层类的类的可读性。
  3. 改善利用的可测试性。
  4. 让您可能划分好职责,从而避免出现大型类。

我感觉对于常见的APP,网域层仿佛并没有必要,对于ViewModel反复的逻辑,应用util来说个别就已足够
或者网域层实用于特地大型的我的项目吧,各位可依据本人的需要选用,对于网域层的详细信息可见:https://developer.android.com…

数据层

数据层次要负责获取与解决数据的逻辑,数据层由多个Repository组成,其中每个Repository可蕴含零到多个Data Source。您应该为利用解决的每种不同类型的数据创立一个Repository类。例如,您能够为与电影相干的数据创立 MoviesRepository 类,或者为与付款相干的数据创立 PaymentsRepository 类。当然为了不便,针对只有一个数据源的Repository,也能够将数据源的代码也写在Repository,后续有多个数据源时再做拆分

数据层跟之前的MVVM架构下的数据层并没用什么区别,这里就不多介绍了,对于数据层的详细信息可见:https://developer.android.com…

总结

相比老版的架构指南,新版次要是减少了网域层并批改了界面层,其中网域层是可选的,各位各依据本人的我的项目需要应用。
而界面层则从MVVM架构变成了MVI架构,强调了数据的单向数据流动状态的集中管理。相比MVVM架构,MVI架构次要有以下长处

  1. 强调数据单向流动,很容易对状态变动进行跟踪和回溯,在数据一致性,可测试性,可维护性上都有肯定劣势
  2. 强调对UI State的集中管理,只须要订阅一个ViewState便可获取页面的所有状态,绝对 MVVM 缩小了不少模板代码
  3. 增加状态只须要增加一个属性,升高了ViewModelView层的通信老本,将业务逻辑集中在ViewModel中,View层只须要订阅状态而后刷新即可

当然在软件开发中没有最好的架构,只有最合适的架构,各位可依据状况选用适宜我的项目的架构,实际上在我看来Google在指南中举荐应用MVI而不再是MVVM,很可能是为了对立AndroidCompose的架构。因为在Compose中并没有双向数据绑定,只有单向数据流动,因而MVI是最适宜Compose的架构。

当然如果你的我的项目中没有应用DataBinding,或者也能够开始尝试一下应用MVI,不应用DataBindingMVVM架构切换为MVI老本不高,切换起来也比较简单,在易用性,数据一致性,可测试性,可维护性等方面都有肯定劣势,后续也能够无缝切换到Compose

如果看完本文对你有播种,请动动你发财的小手,点点赞,你的点赞是我最大的能源。

相干学习视频举荐:

【2021最新版】Android studio装置教程+Android(安卓)零基础教程视频(适宜Android 0根底,Android初学入门)含音视频_哔哩哔哩_bilibili

Android架构设计原理与实战——Jetpack联合MVP组合利用开发一个优良的APP!_哔哩哔哩_bilibili

Android进阶必学:jetpack架构组件—Navigation_哔哩哔哩_bilibili

Android进阶零碎学习——Jetpack先天优良的基因能够防止数据内存透露_哔哩哔哩_bilibili


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:MVVM-成为历史Google-全面倒向-MVI

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址