[译] 使用 Architecture Components 开发 MVVM 应用:MVP 开发者的实践指南

原文:antonioleiva.com/mvvm-vs-mvp…
做者:antonioleiva.com/java

译者说

最近在学习 MVVM 相关的知识,在最新一期的 KotlinWeekly 发现了这篇文章。做者经过按部就班的方式,向咱们阐述如何实现 MVVM,以及如何使用 Android Jetpack Components 组件来构建 MVVM 应用。读完之后,收获颇丰。为了让更多的开发者了解到 MVVM,我斗胆翻译过来,这即是这篇文章的来由。英语渣渣,若有错误,还请指正。android

正文

导语

自从 Google 正式发布了 Android Jetpack Components 架构组件,MVVM 已然成为了 Android Apps 官宣的主流开发模式。我认为是时候,提供一些行之有效的帮助,帮助使用 Mvp 模式的开发者来理解 MVVM 模式。git

若是您碰巧看到这篇博客,可是不知道怎么在 Android 中使用 Mvp 模式,推荐您查看我以前写的关于 Mvp 的博客。github

MVVM vs Mvp - 我须要去重构个人 App 吗?

在至关长的一段时间内,Mvp 彷佛是用来 下降 UI 渲染业务逻辑 之间耦合的最受欢迎的开发模式。可是,如今咱们有了新的选择。安全

许多开发者询问我,是否应该逃避 Mvp,或者当开始新的项目如何设计架构。下面是一些想法:架构

  • Mvp 没有消失。它仍然是彻底有效的开发模式,若是您以前使用它,也能够接着使用。
  • MVVM 做为新的开发模式,不必定更好。但谷歌所作的具体实施是颇有道理的,以前使用 MVP 的缘由是:它与 Android 框架很是吻合,而且上手难度不大。
  • 使用 Mvp 并不意味着,你不可使用 Android Jetpack Components 架构组件。可能 ViewModel 没有多大的做用(它是 Presenter 的替代者),可是其余组件能够在项目中使用。
  • 您不须要当即重构您的 App,若是您对 Mvp 很是满意,请继续享受它。通常来讲,最好保持一个安全,可靠的架构。而不是在项目中使用新的技术栈,毕竟重构是须要成本的。

MVVM 和 MVp 的差别

幸运的是,若是您以前熟悉 Mvp,学习 MVVM 将很是容易!在 Android 开发中,二者只有一点点的差别:框架

在 Mvp 中,PresenterView 经过 接口 联系。 在 MVVM 中,ViewModelView 经过 观察者模式 通讯。mvvm

我知道,若是你曾阅读过维基百科关于 MVVM 的定义。将会发现和我以前所说的彻底不符。可是在 Android 开发领域中,抛开 Databinding 不谈,在我看来,这将是理解 MVVM 的最佳方式。ide

在不使用 Arch Components 的状况下,从 MVp 迁移至 MVVM

我将使用 MVVM 来改造以前的 androidmvp 例子,MVVM 示例代码请戳这里 androidmvvm函数

我暂时不使用 Architecture Components,先本身实现。以后咱们就能够清晰的认识到 Google 新推出的 Android Jetpack Components 是如何工做的,以及如何让开发变得更加高效。

建立一个 Observable 类

当咱们使用 Observable 模式时,须要一个能够观察的类。该类将持有 Observer 和将发送给 Observer 的泛型类型的值, 以及当值发生改变,通知到 Observer

class Observable<T> {

    private var observers = emptyList<(T) -> Unit>()

    fun addObserver(observer: (T) -> Unit) {
        observers += observer
    }

    fun clearObservers() {
        observers = emptyList()
    }

    fun callObservers(newValue: T) {
        observers.forEach {
            it(newValue)
        }
    }
}
复制代码

使用 States 来表示 UI 更改

因为咱们如今没法直接与 View 进行通讯,View 也不知道该怎么显示。我发现一个灵活的方式,经过一个 Model 类来表示 UI 状态。

举个栗子,若是咱们但愿界面显示一个进度条,咱们将发送一个 Loading 状态,消费该状态的方式彻底由视图决定。

对于这种特殊状况,我建立了一个 ScreenState 类,它接受一个表示视图所需状态的泛型类型。

每一个界面都有一些共同的状态,例如 LoadingErroor。而后是每一个界面显示的具体状态。

可使用如下密闭类,来表示通用的 ScreenState

sealed class ScreenState<out T>{
    object Loading:ScreenState<Nothing>()
    class Render<T>(val renderState:T):ScreenState<T>()
}
复制代码

对于特定状态,咱们可能须要额外的定义。对于登录状态,枚举类就足够了。

enum class LoginState{
    Success,
    WrongUserName,
    WrongUserPassword
}
复制代码

可是对于 MainState,咱们正在显示列表和消息,枚举类没法提供足够的支持,因此密闭类再次得到个人青睐(稍后会看到具体缘由)。

sealed class MainState{
    class ShowItems(val items:List<String>):MainState()
    class showMessage(val items:String):MainState()
}
复制代码

将 Presenter 转换为 ViewModel

咱们再也不须要定义 View 接口,你能够摆脱它。由于咱们将使用 Observable 替代。

以下示例:

val stateObservable = Observable<ScreenState<LoginState>>()
复制代码

以后,当咱们想显示进度条表示加载状态时,只须要调用 LoadingStateObserver

fun validateCredentials(username: String, password: String) {
    stateObservable.callObservers(ScreenState.Loading)
    loginInteractor.login(username, password, this)
}
复制代码

当登陆完成时,须要展现成功信息:

override fun onSuccess() {
 stateObservable.callObservers(ScreenState.Render(LoginState.Success))
}
复制代码

老实说,登陆成功的状态能够用不一样的方式实现,若是咱们想要更明确,可使用 LoginState.NavigateToMain 或者相似的方式进入首页。

但这取决于更多因素,取决于应用程序架构。我会这样作。

而后,在 ViewModelonDestroy() 中,咱们清除了 Observers,避免潜在的内存泄漏问题。

在 Activity 中使用 ViewModel

目前 Activity 还没法充当 ViewModel 中 View 的角色,所以 观察者模式 将会受到重用。

首先,初始化 ViewModel

private val viewModel = LoginViewModel(LoginInteractor())
复制代码

以后,在 onCreate() 中观察状态,当状态发生变化,将会调用 updateUI()

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        viewModel.stateObservable.addObserver { updateUI() }
    }
复制代码

在这里,感谢密闭类和枚举类。经过使用 when 表达式,一些变得如此简单。我分两步处理状态:首先是通常状态,而后是特定的 LoginState

第一个 when 表达式分支:显示加载状态的进度条。若是是其它特定状态,须要调用另外的函数处理。

private fun updateUI(it: ScreenState<LoginState>) {
        when (it) {
            ScreenState.Loading -> progressbar.visibility = View.VISIBLE
            is ScreenState.Render -> processLoginState(it.renderState)
        }
    }
复制代码

第二个 when 表达式分支:首先隐藏进度条(若是可见),若是是成功状态,则进入首页。若是是错误状态,则提示相应的错误信息

private fun processLoginState(renderState: LoginState) {
        progressbar.visibility = View.GONE
        when (renderState) {
            LoginState.Success -> startActivity(Intent(this, MainActivity::class.java))
            LoginState.WrongUserName -> username.error = getString(R.string.username_error)
            LoginState.WrongUserPassword -> password.error = getString(R.string.password_error)
        }
    }
复制代码

当点击登陆按钮,调用 ViewModel 中的 onLoginClicked() 进行操做。

private fun login() {
        viewModel.onLoginClicked(username.text.toString(), password.text.toString())
    }
复制代码

而后,在 Activity 中的 onDestroy() 调用 ViewModelonDestroy() 释放资源(这样就能够分离观察者)。

override fun onDestroy() {
        viewModel.onDestroy()
        super.onDestroy()
    }
复制代码

使用 Architecture Components 修改代码

经过以前本身实现 MVVM 的 ViewModel,以便您能够轻松的看到差别。到目前为止,与 MVP 相比,MVVM 并无带来更多的好处。

但也要一些不一样,最重要的一点是您能够忘记 Activity 的销毁,因此您能够脱离它的生命周期,随时作你的工做。特别感谢 ViewModelLiveData。当 Activity 从新建立或者被销毁时,您无需担忧应用的崩溃。

这是工做原理:当 Activity 被从新建立,ViewModel 仍然存在,当 Activity 被永久杀死的时候,将会调用 ViewModelonCleared()

viewmodel-lifecycle.png

因为 LiveData 也具备生命周期意识,所以它知道什么时候跟 LifecycleOwner 创建和断开联系。因此您无需关心它。

我并不打算深刻讲解 Architecture Components 的工做原理(由于在官方的开发者指南中有更深入的解释),因此让咱们继续探索实现 MVVM

在项目中使用 Architecture Components,须要添加如下依赖

implementation "android.arch.lifecycle:extensions:1.1.1"
复制代码

若是您使用其余组件,如:Room 。或者在 AndroidX 上使用这些组件,更多内容请参考 这里

Architecture Components ViewModel

使用 ViewModel 很是简单,你只须要继承 ViewModel 便可。

class LoginViewModel(private val loginInteractor: LoginInteractor) : ViewModel()
复制代码

删除 onDestroy(),由于它再也不须要了。咱们能够将释放资源的代码,转移到 onCleared(),这样咱们就不须要在 ActivityonCreate() 中添加观察,onDestroy() 中移除观察。就和咱们无需关心 onCleared() 的调用时机同样。

override fun onCleared() {
        stateObservable.clearObservers()
        super.onCleared()
    }
复制代码

如今,让咱们回到 LoginActivity 中,建立一个具备延迟属性的 ViewModel,在 onCreate() 中为其分配值。

private lateinit var viewModel: LoginViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        viewModel = ViewModelProviders.of(this)
            .get(LoginViewModel::class.java)
    }
复制代码

ViewModel 不须要经过构造传递参数时,能够按照上述方法实现。可是当咱们须要 ViewModel 经过构造传递参数时,则必须声明一个工厂类。

class LoginViewModelFactory(private val loginInteractor: LoginInteractor) : ViewModelProvider.NewInstanceFactory() {

    override fun <T : ViewModel?> create(modelClass: Class<T>): T =
        LoginViewModel(loginInteractor) as T
}
复制代码

Activity 中经过如下方式获取 ViewModel 实例

override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_login)
        viewModel = ViewModelProviders.of(this, LoginViewModelFactory(LoginInteractor()))
            .get(LoginViewModel::class.java)
    }
复制代码

用 LiveData 替换 Observable

LiveData 能够安全的替换咱们的 Observable 类,须要注意的一点是,LiveData 默认状况是不可变的(您没法改变其值)。

这很棒,由于咱们但愿它是公共的,方便 Observer 能够订阅。但咱们不但愿在其余地方被修改。

可是,另外一方面,数据须要是可变的,否则咱们为何会观察它呢?所以,诀窍是使用一个私有的属性,并提供一个公共的 getter

在 kotlin 中,它将是一个私有的属性,和一个公共的 get() 属性

private val _loginState: MutableLiveData<ScreenState<LoginState>> = MutableLiveData()
复制代码
val loginState: LiveData<ScreenState<LoginState>>
    get() = _loginState
复制代码

并且咱们也再也不须要 onCleared() 了,由于 LiveData 具备生命周期意识,它将在正确的时间中止观察。

要观察它,最简洁的方式以下:

viewModel.loginState.observe(::getLifecycle, ::updateUI)
复制代码

若是你不明白 函数引用,请查看我以前关于 函数引用 的文章。

updateUI() 须要 ScreenState 做为参数,以便它适合 LiveData 的返回值。我能够将它用做函数引用。

private fun updateUI(screenState: ScreenState<LoginState>?) {
    ...
}
复制代码

MainViewModel 也不须要 onResume() 了,相反,咱们能够重写属性的 getter,并在 LiveData 第一次观察时,执行请求。

private lateinit var _mainState: MutableLiveData<ScreenState<MainState>>
 
val mainState: LiveData<ScreenState<MainState>>
    get() {
        if (!::_mainState.isInitialized) {
            _mainState = MutableLiveData()
            _mainState.value = ScreenState.Loading
            findItemsInteractor.findItems(::onItemsLoaded)
        }
        return _mainState
    }
复制代码

MainActivity 的代码和以前的相似。

viewModel.mainState.observe(::getLifecycle, ::updateUI)
复制代码

注意

以前的代码彷佛有点复杂,主要是由于使用了新的框架,当您了解它是如何工做的,一切将变得很是简单。

确定有一些新的样板代码,例如 ViewModelFactory 和 获取 ViewModel,或防止外部人员使用 LiveData 所定义的两个属性。我经过使用 Kotlin 的一些特性简化了本文的一些内容,可使您的代码更加简洁,为了简单起见,我并不打算在这里添加它们。

正如我在开头所说的,您是否使用 MVVM 或者 MVP 彻底取决于您本身。若是您目前的架构使用 Mvp 运行良好,我认为没有重构的冲动,但了解 MVVM 的工做原理颇有意思。由于您早晚会须要它。

我认为咱们仍在探索,在 Android 中使用 MVVM 和架构组件最优的解决方案,我相信个人方案并不完美。因此,请让我听到您心里不一样的声音,我很乐意根据反馈更新文章。

您能够在 GitHub 查看完整的代码示例,(请 star 支持 )

相关文章
相关标签/搜索