正文
简评:继续
第一部分的文章
,作者在第二部分中使用的技术包括 MVVM,RxJava2.
5. MVVM 架构 + 存储库模式 + Android 管理封装器
关于 Android 世界的一点点架构知识
长时间以来,Android 开发者们在他们的项目中没有使用任何类型的架构。近三年以来,架构在 Android 开发者社区中被炒得天花乱坠。Activity 之神的时代已经过去了,Google 发布了 Android 架构蓝图仓库,提供了许多样例和说明来实现不同的架构方式。最终,在 Google IO 17 大会,他们介绍了
Android 架构组件
,这些库的集合帮助我们编写更清晰的代码和更好的 app。你可以使用所有的组件,也可以使用其中的部分。但是,我觉得它们都挺有用的。接下来我们将使用这些组件。我会先解决这些问题,然后用这些组件和库来重构代码,看看这些库解决了哪些问题。
有两种主要的
架构模式
分离了 GUI 代码:
很难说哪一种更好。你应该两种都尝试一下,然后再做出决定。我倾向于使用管理生命周期组件的 MVVM 模式,并且接下来我会介绍它。如果你没试过 MVP,在 Medium 上有大量很好的关于它的文章。
什么是 MVVM 模式?
MVVM
模式是一种
架构模式
。它代表 Model-View-ViewModel。我觉得这个名字会让开发者困扰。如果我是那个命名的人,我会称之为 View-ViewModel-Model,因为 ViewModel 是连接视图和模型的中间件。
-
View
是你的 Activity,Fragment 或者其他 Android 自定义视图的抽象。注意千万别和 Android View 混淆了。这个 View 应该是个哑巴,我们不能在 View 里写任何逻辑。视图不应该持有任何数据。它应该有一个 ViewModel 实例,所有 View 需要的数据应该从这个实例中获取。同时,View 应该观察这些数据,一旦 ViewModel 中的数据发生了变化,布局就会发生改变。
View
有一个职责:不同的数据和状态下,布局会怎样显示。
-
ViewModel
是持有数据和逻辑类的一个抽象,负责什么时候获取数据,什么时候传递数据。ViewModel 保留当前的
状态
。同时,ViewModel 持有一个或多个
Model
实例,所有的数据都从这些实例中获取。它不应该知道这些数据是从数据库中获取的还是从远程服务器中获取的。此外,ViewModel 也不应该知道关于 View 的一切。而且,ViewModel 也不应该知道任何关于 Android 框架的事。
-
Model
是为 ViewModel 准备数据的抽象层。它是那些从远程服务器或者内存缓存或者本地数据库获取数据的类。这和那些 User,Car,Square 等等是不一样的。模型类知识一些保存数据的类。通常,它是仓库模式的一种实现,我们接下来将会讲到。Model 不该知道关于 ViewModel 的一切。
MVVM,如果正确实现,将是一种很好的分离代码的方式,这样也会让它更容易测试。它帮助我们遵循
SOLID 原则
,因此我们的代码更容易维护。
现在我将写一个最简单的示例来解释它是怎样工作的。
首先,创建一个简单的
Model
来返回字符串:
class RepoModel {
fun refreshData() : String {
return "Some new data"
}
}
通常,获取数据是异步的,所以我们必须等待一下。为了模拟这种情况,我把它改成下面这样:
class RepoModel {
fun refreshData(onDataReadyCallback: OnDataReadyCallback) {
Handler().postDelayed({ onDataReadyCallback.onDataReady("new data") },2000)
}
}
interface OnDataReadyCallback {
fun onDataReady(data : String)
}
首先,我创建了
OnDataReadyCallback
接口,它有个
onDataReady
函数。现在,我们的
refreshData
函数实现了
OnDataReadyCallback 。
为了模拟等待,我使用了
Handler
。2 秒后,
OnDataReadyCallback
的实现将会调用
onDataReady
函数。
现在来创建我们的
ViewModel
:
class MainViewModel {
var repoModel: RepoModel = RepoModel()
var text: String = ""
var isLoading: Boolean = false
}
可以看到,有一个
RepoModel
的示例,即将展示的
text
以及保存当前状态的
isLoading
。现在,创建一个
refresh
函数,用来获取数据:
class MainViewModel {
...
val onDataReadyCallback = object : OnDataReadyCallback {
override fun onDataReady(data: String) {
isLoading.set(false)
text.set(data)
}
}
fun refresh(){
isLoading.set(true)
repoModel.refreshData(onDataReadyCallback)
}
}
refresh
函数调用了 RepoModel 中的
refreshData
,传递了一个实现
OnDataReadyCallback
接口的实例。好,那么什么是对象呢?无论何时,当你想实现一些接口或者继承一些类而不用创建子类时,你都会使用对象声明。如果你想要使用匿名类呢?在这里,你需要使用
object
表达式:
class MainViewModel {
var repoModel: RepoModel = RepoModel()
var text: String = ""
var isLoading: Boolean = false
fun refresh() {
repoModel.refreshData( object : OnDataReadyCallback {
override fun onDataReady(data: String) {
text = data
})
}
}
当我们调用 refresh,我们应该把 view 改成加载中的状态,并且一旦获取到数据,就把
isLoading
设置为 false。同时,我们应该把
text
改成
ObservableField<String>
,把
isLoading
改成
ObservableField<Boolean>
。
ObservableField
是一个数据绑定库中的类,我们可以用它来创建一个可观察对象。它把对象包裹成可被观察的。
class MainViewModel {
var repoModel: RepoModel = RepoModel()
val text = ObservableField<String>()
val isLoading = ObservableField<Boolean>()
fun refresh(){
isLoading.set(true)
repoModel.refreshData(object : OnDataReadyCallback {
override fun onDataReady(data: String) {
isLoading.set(false)
text.set(data)
}
})
}
}
注意到我使用了
val
而不是
var
,因为我们仅在字段里改变它的值,而不是字段本身。如果你想要初始化的话,应该这样:
val text = ObservableField("old data")
val isLoading = ObservableField(false)
现在改变我们的布局,让它可以观察
text
和
isLoading
。首先,我们会绑定
MainViewModel
而不是
Repository:
<data>
<variable
name="viewModel"
type="me.fleka.modernandroidapp.MainViewModel" />
</data>
然后:
-
让 TextView 观察
MainViewModel
中的
text
-
添加一个 ProgressBar,当
isLoading
为 true 时才会显示
-
添加一个 Button,在 onClick 中调用
refresh
函数,仅当
isLoading
为 false 时才能点击
...
<TextView
android:id="@+id/repository_name"
android:text="@{viewModel.text}"
...
/>
...
<ProgressBar
android:id="@+id/loading"
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}"
...
/>
<Button
android:id="@+id/refresh_button"
android:onClick="@{() -> viewModel.refresh()}"
android:clickable="@{viewModel.isLoading ? false : true}"
/>
...
如果现在运行的话,你会得到一个错误,因为如果没有导入 View 的话,
View.VISIBLE
和
View.GONE
不能使用。所以,我们应该导入:
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type="me.fleka.modernandroidapp.MainViewModel" />
</data>
好,布局完成了。现在我们来完成绑定。如我们所说的
View
应该持有
ViewModel
的实例:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
var mainViewModel = MainViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewModel = mainViewModel
binding.executePendingBindings()
}
}
最终,我们的运行效果:
可以看到
old data
变成了
new data
。
这就是最简单的 MVVM 示例。
还有一个问题,让我们来旋转手机:
new data
又变成了
old data
。怎么可能?看下 Activity 的生命周期:
一旦你旋转屏幕,新的 Activity 实例就会创建,
onCreate()
方法会被调用。现在,看下我们的 Activity:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
var mainViewModel = MainViewModel()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewModel = mainViewModel
binding.executePendingBindings()
}
}
如你所见,一旦创建了一个新的 Activity 实例,
MainViewModel
的实例也被创建了。如果每次重新创建的 MainActivity 都有一个相同的
MainViewModel
实例会不会好点?
隆重推出生命周期感知组件
因为许多的开发者面临这个问题,Android 框架团队的开发者决定创建一个库来帮我们解决这个问题。
ViewModel
类是其中一个。我们所有的 ViewModel 类都应该继承自它。
让我们的
MainViewModel
继承来自于生命周期感知组件的
ViewModel
。首先,我们需要在 build.gradle 文件中添加依赖:
dependencies {
...
implementation "android.arch.lifecycle:runtime:1.0.0-alpha9"
implementation "android.arch.lifecycle:extensions:1.0.0-alpha9"
kapt "android.arch.lifecycle:compiler:1.0.0-alpha9"
}
然后继承 ViewModel:
package me.fleka.modernandroidapp
import android.arch.lifecycle.ViewModel
class MainViewModel : ViewModel() {
...
}
在 MainActivity 的 onCreate() 方法中,你应该这样:
class MainActivity : AppCompatActivity() {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
binding.executePendingBindings()
}
}
注意到我们已经没有创建
MainViewModel
的实例了。现在,我们从
ViewModelProviders
中获取它。
ViewModelProviders
是一个功能类,有一个获取
ViewModelProvider
的方法。和
作用范围
相关,所以,如果你在 Activity 调用
ViewModelProviders.of(this)
,那么你的
ViewModel
会存活直到 Activity 被销毁(被销毁而且没有被重新创建)。类似地,如果你在 Fragment 中调用,你的
ViewModel
也会存活直到 Fragment 被销毁。看下下面的图解:
ViewModelProvider
的职责是在第一次调用的时候创建实例,并在 Activity/Fragment 重新创建时返回旧的实例。
不要混淆了:
MainViewModel::class.java
在 Kotlin 中,如果你仅仅写成:
MainViewModel::class
它会返回一个
KClass
,和 Java 中的 Class 不一样。因此,如果我们加上
.java
,它表示:
返回一个和给定的 KClass 实例关联的
Java 类
实例。
现在让我们来旋转一下屏幕看看会发生什么:
我们的数据和旋转之前一样。
上一篇文章
中,我说过我们的 app 将会获取 GitHub 仓库列表并展示。要想完成它,我们需要添加
getRepositories
函数,它会返回一个伪造的仓库列表:
class RepoModel {
fun refreshData(onDataReadyCallback: OnDataReadyCallback) {
Handler().postDelayed({ onDataReadyCallback.onDataReady("new data") },2000)
}
fun getRepositories(onRepositoryReadyCallback: OnRepositoryReadyCallback) {
var arrayList = ArrayList<Repository>()
arrayList.add(Repository("First", "Owner 1", 100 , false))
arrayList.add(Repository("Second", "Owner 2", 30 , true))
arrayList.add(Repository("Third", "Owner 3", 430 , false))
Handler().postDelayed({ onRepositoryReadyCallback.onDataReady(arrayList) },2000)
}
}
interface OnDataReadyCallback {
fun onDataReady(data : String)
}
interface OnRepositoryReadyCallback {
fun onDataReady(data : ArrayList<Repository>)
}
同时,我们的
MainViewModel
会有一个调用
getRepositories
的函数:
class MainViewModel : ViewModel() {
...
var repositories = ArrayList<Repository>()
fun refresh(){
...
}
fun loadRepositories(){
isLoading.set(true)
repoModel.getRepositories(object : OnRepositoryReadyCallback{
override fun onDataReady(data: ArrayList<Repository>) {
isLoading.set(false)
repositories = data
}
})
}
}
最后,我们需要在 RecyclerView 中展示这些仓库。要这么做,我们必须:
-
创建
rv_item_repository.xml
布局
-
在
activity_main.xml
布局中添加
RecyclerView
-
创建 RepositoryRecyclerViewAdapter
-
set adapter
创建
rv_item_repository.xml
我将使用 CardView 库,所以我们要在 build.gradle 中添加依赖:
implementation 'com.android.support:cardview-v7:26.0.1'
布局看起来是这样的:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View" />
<variable
name="repository"
type="me.fleka.modernandroidapp.uimodels.Repository" />
</data>
<android.support.v7.widget.CardView
android:layout_width="match_parent"
android:layout_height="96dp"
android:layout_margin="8dp">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/repository_name"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:text="@{repository.repositoryName}"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintHorizontal_bias="0.0"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="0.083"
tools:text="Modern Android App" />
<TextView
android:id="@+id/repository_has_issues"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="@string/has_issues"
android:textStyle="bold"
android:visibility="@{repository.hasIssues ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toBottomOf="@+id/repository_name"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1.0"
app:layout_constraintStart_toEndOf="@+id/repository_name"
app:layout_constraintTop_toTopOf="@+id/repository_name"
app:layout_constraintVertical_bias="1.0" />
<TextView
android:id="@+id/repository_owner"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:text="@{repository.repositoryOwner}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/repository_name"
app:layout_constraintVertical_bias="0.0"
tools:text="Mladen Rakonjac" />
<TextView
android:id="@+id/number_of_starts"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:layout_marginEnd="16dp"
android:layout_marginStart="16dp"
android:layout_marginTop="8dp"
android:text="@{String.valueOf(repository.numberOfStars)}"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/repository_owner"
app:layout_constraintVertical_bias="0.0"
tools:text="0 stars" />
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>
</layout>
下一步,在
activity_main.xml
中添加 RecyclerView。别忘了添加依赖:
implementation 'com.android.support:recyclerview-v7:26.0.1'
接下来是布局:
<?xml version="1.0" encoding="utf-8"?>
<layout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools">
<data>
<import type="android.view.View"/>
<variable
name="viewModel"
type="me.fleka.modernandroidapp.MainViewModel" />
</data>
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.fleka.modernandroidapp.MainActivity">
<ProgressBar
android:id="@+id/loading"
android:layout_width="48dp"
android:layout_height="48dp"
android:indeterminate="true"
android:visibility="@{viewModel.isLoading ? View.VISIBLE : View.GONE}"
app:layout_constraintBottom_toTopOf="@+id/refresh_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<android.support.v7.widget.RecyclerView
android:id="@+id/repository_rv"
android:layout_width="0dp"
android:layout_height="0dp"
android:indeterminate="true"
android:visibility="@{viewModel.isLoading ? View.GONE : View.VISIBLE}"
app:layout_constraintBottom_toTopOf="@+id/refresh_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
tools:listitem="@layout/rv_item_repository" />
<Button
android:id="@+id/refresh_button"
android:layout_width="160dp"
android:layout_height="40dp"
android:layout_marginBottom="8dp"
android:layout_marginEnd="8dp"
android:layout_marginStart="8dp"
android:layout_marginTop="8dp"
android:onClick="@{() -> viewModel.loadRepositories()}"
android:clickable="@{viewModel.isLoading ? false : true}"
android:text="Refresh"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintVertical_bias="1.0" />
</android.support.constraint.ConstraintLayout>
</layout>
我们删除了一些之前创建的 TextView 元素,并且按钮现在触发的是
loadRepositories
而不是
refresh:
<Button
android:id="@+id/refresh_button"
android:onClick="@{() -> viewModel.loadRepositories()}"
...
/>
删掉 MainViewModel 中的 refresh 和 RepoModel 中的 refreshData 函数。
现在,为 RecyclerView 添加一个适配器:
class RepositoryRecyclerViewAdapter(private var items: ArrayList<Repository>,
private var listener: OnItemClickListener)
: RecyclerView.Adapter<RepositoryRecyclerViewAdapter.ViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): ViewHolder {
val layoutInflater = LayoutInflater.from(parent?.context)
val binding = RvItemRepositoryBinding.inflate(layoutInflater, parent, false)
return ViewHolder(binding)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int)
= holder.bind(items[position], listener)
override fun getItemCount(): Int = items.size
interface OnItemClickListener {
fun onItemClick(position: Int)
}
class ViewHolder(private var binding: RvItemRepositoryBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(repo: Repository, listener: OnItemClickListener?) {
binding.repository = repo
if (listener != null) {
binding.root.setOnClickListener({ _ -> listener.onItemClick(layoutPosition) })
}
binding.executePendingBindings()
}
}
}
ViewHolder 接受
RvItemRepositoryBinding
类型的实例,而不是 View 类型,这样我们就能在 ViewHolder 中为每一项实现数据绑定。同时,别被下面一行函数给弄迷糊了:
override fun onBindViewHolder(holder: ViewHolder, position: Int) = holder.bind(items[position], listener)
它只是这种形式的缩写:
override fun onBindViewHolder(holder: ViewHolder, position: Int){
return holder.bind(items[position], listener)
}
并且
items[position]
实现了索引操作,和
items.get(position)
是一样的。
还有一行可能会迷惑的代码:
binding.root.setOnClickListener({ _ -> listener.onItemClick(layoutPosition) })
你可以用
_
来代替参数,如果你不需要用它的话。
我们添加了适配器,但在 MainActivity 中还没有把它设置到 RecyclerView 中:
class MainActivity : AppCompatActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener {
lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
binding.viewModel = viewModel
binding.executePendingBindings()
binding.repositoryRv.layoutManager = LinearLayoutManager(this)
binding.repositoryRv.adapter = RepositoryRecyclerViewAdapter(viewModel.repositories, this)
}
override fun onItemClick(position: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
让我们来运行试试:
很奇怪。发生了啥?
-
Activity 被创建了,所以新的适配器也创建了,但里面的
repositories
实际上是空的
-
我们点击了按钮
-
调用了 loadRepositories 函数,显示了进度条
-
2 秒后,我们拿到了仓库列表,隐藏了进度条,但仓库列表没显示。因为没有调用
notifyDataSetChanged
-
一旦我们旋转屏幕,新的 Activity 被创建,带有仓库参数的新的适配器也被创建了,所以实际上 viewModel 是有数据的。
那么,
MainViewModel
该怎样才能通知
MainActivity
更新了项目,好让我们可以调用
notifyDataSetChanged
呢?
不应该这样做。
这点非常重要:
MainViewModel
不应该知道任何关于
MainActivity
的东西。
MainActivity
才拥有
MainViewModel
实例,所以应该让它来监听数据变化并通知
Adapter
。那怎么做?
我们可以观察
repositories
,这样一旦数据改变了,我们就能改变我们的 adapter。
这个方案中可能出错的地方?
我们先来看看下面的场景:
-
在 MainActivity 中,我们观察了
repositories
:一旦改变,我们调用
notifyDataSetChanged
-
我们点击了按钮
-
当我们等待数据改变时,
MainActivity
可能会因为
配置改变
而被重新创建
-
我们的
MainViewModel
依然存在
-
2 秒后,我们的
repositories
获得新的数据,然后通知观察者数据已经改变
-
观察者尝试调用不再存在的
adapter
的
notifyDataSetChanged
,因为
MainActivity
已经重新创建了
所以,我们的方案还不够好。
介绍 LiveData
LiveData
是另一个生命周期感知的组件。它能观察 View 的生命周期。这样一来,一旦 Activity 因为配置改变而被销毁,LiveData 就能够知道,它也就能够从被销毁的 Activity 中回收观察者。
让我们在
MainViewModel
中实现它:
class MainViewModel : ViewModel() {
var repoModel: RepoModel = RepoModel()
val text = ObservableField("old data")
val isLoading = ObservableField(false)
var repositories = MutableLiveData<ArrayList<Repository>>()
fun loadRepositories() {
isLoading.set(true)
repoModel.getRepositories(object : OnRepositoryReadyCallback {
override fun onDataReady(data: ArrayList<Repository>) {
isLoading.set(false)
repositories.value = data
}
})
}
}
然后在 MainActivity 中观察改动:
class MainActivity : LifecycleActivity(), RepositoryRecyclerViewAdapter.OnItemClickListener {
private lateinit var binding: ActivityMainBinding
private val repositoryRecyclerViewAdapter = RepositoryRecyclerViewAdapter(arrayListOf(), this)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
val viewModel = ViewModelProviders.of(this).get(MainViewModel::class.java)
binding.viewModel = viewModel
binding.executePendingBindings()
binding.repositoryRv.layoutManager = LinearLayoutManager(this)
binding.repositoryRv.adapter = repositoryRecyclerViewAdapter
viewModel.repositories.observe(this,
Observer<ArrayList<Repository>> { it?.let{ repositoryRecyclerViewAdapter.replaceData(it)} })
}
override fun onItemClick(position: Int) {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
}
it
关键字是什么意思呢?如果某个函数只有一个参数,那么那个参数就可以用
it
来代替。假设我们有个乘以 2 的 lambda 表达式:
((a) -> 2 * a)
我们可以替换成这样:
(it * 2)
如果你现在运行,你会看到一切都正常工作了:
为什么相比 MVP 我更倾向于 MVVM?
-
没有 View 的无聊的接口,因为 ViewModel 没有 View 的引用。
-
没有 Presenter 的无聊的接口,因为根本不需要。
-
更容易处理配置改动。
-
使用 MVVM,Activity,Fragment 里的代码更少。
存储库模式