# 前言

经常在 medium.com 上看到一些高质量的技术帖子,但是由于国内的上网环境或者有的同学对于看英文比较排斥,错过了不少好文章。因此,西哥决定弄一个《优质译文专栏》,花一些时间翻译一些优质技术文给大家。这篇文章是一个小系列,用 Kotlin 开发现代 Android APP, 总共四篇,后面的会陆续翻译!以下是正文。

现在,真的很难找到一个涵盖所有 Android 新技术的项目,因此我决定自己来写一个,在本文中,我们将用到如下技术:

  • 0 、Android Studio
  • 1、Kotlin 语言
  • 2、构建变体
  • 3、ConstraintLayout
  • 4、DataBinding 库
  • 5、MVVM+repository+Android Manager 架构模式
  • 6、RxJava2 及其对架构的帮助
  • 7、Dagger 2.11,什么是依赖注入?为什么要使用它?
  • 8、Retrofit + RxJava2 实现网络请求
  • 9、RooM + RxJava2 实现储存
# 我们的 APP 最终是什么样子?

我们的 APP 是一个非常简单的应用程序,它涵盖了上面提到的所有技术。只有一个简单的功能:从 Github 获取 googlesamples 用户下的所有仓库,将数据储存到本地数据库,然后在界面展示它。

我将尝试解释更多的代码,你也可以看看你 Github 上的代码提交。

Github:github.com/mladenrakon…

让我们开始吧。

# 0、Android Studio

首先安卓 Android Studio 3 beta 1 (注:现在最新版为 Android Studio 4.0),Android Studio 已经支持 Kotlin,去到 Create Android Project 界面,你将在此处看到新的内容:带有标签的复选框 include Kotlin support 。默认情况下选中。按两次下一步,然后选择 EmptyActivity ,然后完成了。 恭喜!你用 Kotlin 开发了第一个 Android app)

# 1、Kotlin

在刚才新建的项目中,你可以看到一个 MainActivity.kt :

1
2
3
4
5
6
7
8
9
10
11
12
package me.mladenrakonjac.modernandroidapp

import android.support.v7.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}

.kt 后缀代表了这是一个 Kotlin 文件

MainActivity : AppCompatActivity() 表示我们的 MainActivity 继承自 AppCompatActivity

此外,所有的方法都必须有一个关键字 fun , 在 Kotlin 中,你不能使用 @override 注解,如果你要表明方法是复写父类或者接口的方法的话,直接使用 override 关键字,注意:它和 Java 不一样,不是一个注解了。

然后, savedInstanceState: Bundle? 中的 ? 代表什么呢?它代表了 savedInstanceState 这个参数可以是 Bundle 或者 null。Kotlin 是一门 null 安全语言,如果你像下面这样写:

1
var a : String

你将会得到一个编译错误。因为 a 变量必须被初始化,并且不能为 null,因此你要像这样写:

1
var a : String = "Init value"

并且,如果你执行以下操作,也会报编译错误:

1
a = null

要想使 a 变量为 null , 你必须这样写:

1
var a : String?

为什么这是 Kotlin 语言的一个重要功能呢?因为它帮我们避免了 NPE,Androd 开发者已经对 NPE 感到厌倦了,甚至是 null 的发明者 - Tony Hoare 先生,也为发明它而道歉。假设我们有一个可以为空的 nameTextView 。如果为 null,以下代码将会发生 NPE:

1
nameTextView.setEnabled(true)

但实际上,Kotlin 做得很好,它甚至不允许我们做这样的事情。它会强制我们使用 ? 或者 !! 操作符。如果我们使用 ? 操作符:

1
nameTextView?.setEnabled(true)

仅当 nameTextView 不为 null 时,这行代码才会继续执行。另一种情况下,如果我们使用 !! 操作符:

1
nameTextView!!.setEnabled(true)

如果 nameTextView 为 null,它将为我们提供 NPE。它只适合喜欢冒险的家伙)

这是对 Kotlin 的一些介绍。我们继续进行,我将停止描述其他 Kotlin 特定代码。

# 2、构建变体

通常,在开发中,如果你有两套环境,最常见的是测试环境和生产环境。这些环境在服务器 URL图标名称目标api 等方面可能有所不同。通常,在开始的每个项目中我都有以下内容:

  • finalProduction : 上传 Google Play 使用
  • demoProduction : 该版本使用生产环境服务器 Url, 并且它有着 GP 上的版本没有的新功能,用户可以在 Google play 旁边安装,然后可以进行新功能测试和提供反馈。
  • demoTesting : 和 demoProduction 一样,只不过它用的是测试地址
  • mock : 对于我来说,作为开发人员和设计师而言都是很有用的。有时我们已经准备好设计,而我们的 API 仍未准备好。等待 API 准备就绪后再开始开发可不是好的解决方案。此构建变体为提供有 mock 数据,因此设计团队可以对其进行测试并提供反馈。对于保证项目进度真的很有帮助,一旦 API 准备就绪,我们便将开发转移到 demoTesting 环境。

在此应用程序中,我们将拥有所有这些变体。它们的 applicationId 和名称不同。 gradle 3.0.0 flavourDimension 中有一个新的 api ,可让您混合不同的产品风味,因此您可以混合 demominApi23 风味。在我们的应用程序中,我们将仅使用 “默认” 的 flavorDimension 。早 app 的 build.gradle 中,将此代码插入 android {} 下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
flavorDimensions "default"

productFlavors {

finalProduction {
dimension "default"
applicationId "me.mladenrakonjac.modernandroidapp"
resValue "string", "app_name", "Modern App"
}

demoProduction {
dimension "default"
applicationId "me.mladenrakonjac.modernandroidapp.demoproduction"
resValue "string", "app_name", "Modern App Demo P"
}

demoTesting {
dimension "default"
applicationId "me.mladenrakonjac.modernandroidapp.demotesting"
resValue "string", "app_name", "Modern App Demo T"
}


mock {
dimension "default"
applicationId "me.mladenrakonjac.modernandroidapp.mock"
resValue "string", "app_name", "Modern App Mock"
}
}

打开 string.xml 文件,删掉 app_name string 资源,因此,我们才不会发生资源冲突,然后点击 Sync Now , 如果转到屏幕左侧的 “构建变体” ,则可以看到 4 个不同的构建变体,其中每个都有两种构建类型:“Debug” 和 “Release”, 切换到 demoProduction 构建变体并运行它。然后切换到另一个并运行它。您就可以看到两个名称不同的应用程序。

img

# 3、ConstraintLayout

如果你打开 activity_main.xml , 你可以看到跟布局是 ConstraintLayout , 如果你开发过 iOS 应用程序,你可能知道 AutoLayoutConstraintLayout 和它非常的相似,他们甚至用了相同的 Cassowary 算法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">

<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Hello World!"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />

Constraints 可以帮我们描述 View 之间的关系。对于每一个 View 来说,应该有 4 个约束,每一边一个约束,在这种情况下,我们的 View 就被约束在了父视图的每一边了。

在 Design Tab 中,如果你将 Hello World 文本稍微向上移动,则在 Text Tab 中将增加下面这行代码:

1
app:layout_constraintVertical_bias="0.28"

img

Design tab 和 Text tab 是同步的,我们在 Design 中移动视图,则会影响 Text 中的 xml ,反之亦然。垂直偏差描述了视图对其约束的垂直趋势。如果要使视图垂直居中,则应使用:

1
app:layout_constraintVertical_bias="0.28"

我们让 Activity 只显示一个仓库,它有仓库的名字,star 的数量,作者,并且还会显示是否有 issue

img

要得到上面的布局设计,代码如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout 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"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">

<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: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"
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:layout_marginTop="8dp"
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"
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>

不要被 tools:text 搞迷惑了,它的作用仅仅是让我们可以预览我们的布局。

我们可以注意到,我们的布局是扁平的,没有任何嵌套,你应该尽量少的使用布局嵌套,因为它会影响我们的性能。ConstraintLayout 也可以在不同的屏幕尺寸下正常工作。

img

我有种预感,很快就能达到我们想要的布局效果了。

上面只是一些关于 ConstraintLayout 的少部分介绍,你也可以看一下关于 ConstraintLayout 使用的 google code lab: codelabs.developers.google.com/codelabs/co…

# 4. Data binding library

当我听到 Data binding 库的时候,我的第一反应是:Butterknife 已经很好了,再加上,我现在使用一个插件来从 xml 中获取 View, 我为啥要改变,来使用 Data binding 呢?但当我对 Data binding 有了更多的了解之后,我的它的感觉就像我第一次见到 Butterknife 一样,无法自拔。

# Butterknife 能帮我们做啥?

ButterKnife 帮助我们摆脱无聊的 findViewById 。因此,如果您有 5 个视图,而没有 Butterknife,则你有 5 + 5 行代码来绑定您的视图。使用 ButterKnife,您只有我行代码就搞定。就是这样。

# Butterknife 的缺点是什么?

Butterknife 仍然没有解决代码可维护问题,使用 ButterKnife 时,我经常发现自己遇到运行时异常,这是因为我删除了 xml 中的视图,而没有删除 Activity/Fragment 类中的绑定代码。另外,如果要在 xml 中添加视图,则必须再次进行绑定。真的很不好维护。你将浪费大量时间来维护 View 绑定。

# 那与之相比,Data Binding 怎么样呢?

有很多好处,使用 Data Binding,你可以只用一行代码就搞定 View 的绑定,让我们看看它是如何工作的,首先,先将 Data Binding 添加到项目:

1
2
3
4
5
6
7
8
9
10
11
12
// at the top of file 
apply plugin: 'kotlin-kapt'


android {
//other things that we already used
dataBinding.enabled = true
}
dependencies {
//other dependencies that we used
kapt "com.android.databinding:compiler:3.0.0-beta1"
}

请注意,数据绑定编译器的版本与项目 build.gradle 文件中的 gradle 版本相同:

1
classpath 'com.android.tools.build:gradle:3.0.0-beta1'

然后,点击 Sync Now , 打开 activity_main.xml , 将 Constraint Layout 用 layout 标签包裹

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
<?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">

<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="me.mladenrakonjac.modernandroidapp.MainActivity">

<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: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"
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:layout_marginTop="8dp"
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"
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>

</layout>

注意,你需要将所有的 xml 移动到 layout 标签下面,然后点击 Build 图标或者使用快捷键 Cmd + F9 , 我们需要构建项目来使 Data Binding 库为我们生成 ActivityMainBinding 类,后面在 MainActivity 中将用到它。

如果没有重新编译项目,你是看不到 ActivityMainBinding 的,因为它在编译时生成。

我们还没有完成绑定,我们只是定义了一个非空的 ActivityMainBinding 类型的变量。你会注意到我没有把 ? 放在 ActivityMainBinding 的后面,而且也没有初始化它。这怎么可能呢? lateinit 关键字允许我们使用非空的延迟被初始化的变量。和 ButterKnife 类似,在我们的布局准备完成后,初始化绑定需要在 onCreate 方法中进行。此外,你不应该在 onCreate 方法中声明绑定,因为你很有可能在 onCreate 方法外使用它。我们的 binding 不能为空,所以这就是我们使用 lateinit 的原因。使用 lateinit 修饰,我们不需要在每次访问它的时候检查 binding 变量是否为空。

我们初始化 binding 变量,你需要替换:

1
setContentView(R.layout.activity_main)

为:

1
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

就是这样,你成功的绑定了所有 View, 现在你可以访问它并且做一些更改,例如,我们将仓库名字改为 Modern Android Medium Article :

1
binding.repositoryName.text = "Modern Android Medium Article"

如你所见,现在我们可以通过 bingding 变量来访问 main_activity.xml 的所有 View 了(前提是它们有 id), 这就是 Data Binding 比 ButterKnife 好用的原因。

# kotlin 的 Getters 和 setters

大概,你已经注意到了,我们没有像 Java 那样使用 .setText() ,我想在这里暂停一下,以说明与 Java 相比,Kotlin 中的 getter 和 setter 方法如何工作的。

首先,你需要知道,我们为什么要使用 getters 和 setters,我们用它来隐藏类中的变量,仅允许使用方法来访问这些变量,这样我们就可以向用户隐藏类中的细节,并禁止用户直接修改我们的类。假设我们用 Java 写了一个 Square 类:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class Square {
private int a;

Square(){
a = 1;
}

public void setA(int a){
this.a = Math.abs(a);
}

public int getA(){
return this.a;
}

}

使用 setA() 方法,我们禁止了用户向 Square 类的 a 变量设置一个负数,因为正方形的边长一定是正数,要使用这种方法,我们必须将其设为私有,因此不能直接设置它。这也意味着我们不能直接获得 a ,需要给它定一个 get 方法来返回 a ,如果有 10 个变量,那么我们就得定义 10 个相似的 get 方法,写这样无聊的样板代码,通常会影响我们的心情。

Kotling 使我们的开发人员更轻松了。如果你调用下面的代码:

1
var side: Int = square.a

这并不意味着你是在直接访问 a 变量,它和 Java 中调用 getA() 是相同的

1
int side = square.getA();

因为 Kotlin 自动生成默认的 getter 和 setter。在 Kotlin 中,只有当您有特殊的 setter 或 getter 时,才应指定它。否则,Kotlin 会为您自动生成:

1
2
var a = 1
set(value) { field = Math.abs(value) }

field ? 这又是个什么东西?为了更清楚明白,请看下面代码:

1
2
var a = 1
set(value) { a = Math.abs(value) }

这表明你在调用 set 方法中的 set(value){} ,因为 Kotlin 的世界中,没有直接访问属性,这就会造成无限递归,当你调用 a = something , 会自动调用 set 方法。使用 filed 就能避免无限递归,我希望这能让你明白为什么要用 filed 关键字,并且了解 getters 和 setters 是如何工作的。

回到代码中继续,我将向你介绍 Kotlin 语言的另一个重要功能:apply 函数:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
class MainActivity : AppCompatActivity() {

lateinit var binding: ActivityMainBinding

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
binding.apply {
repositoryName.text = "Medium Android Repository Article"
repositoryOwner.text = "Mladen Rakonjac"
numberOfStarts.text = "1000 stars"

}
}
}

apply 允许你在一个实例上调用多个方法,我们仍然还没有完成数据绑定,还有更棒的事儿,让我们为仓库定义一个 UI 模型(这个是 github 仓库的数据模型 Repository, 它持有要展示的数据,请不要和 Repository 模式的中的 Repository 搞混淆了哈),要创建一个 Kotlin class,点击 New -> Kotlin File/Class :

1
class Repository(var repositoryName: String?,var repositoryOwner: String?,var numberOfStars: Int? ,var hasIssues: Boolean = false)

在 Kotlin 中,主构造函数是类头的一部分,如果你不想定义次构造函数,那就是这样了,数据类到此就完成了,构造函数没有参数分配给字段,没有 setters 和 getters, 整个类就一行代码。

回到 MainActivity.kt ,为 Repository 创建一个实例。

1
2
var repository = Repository("Medium Android Repository Article",
"Mladen Rakonjac", 1000, true)

你应该注意到了,创建类实例,没有用 new

现在,我们在 activity_main.xml 中添加 data 标签。

1
2
3
4
5
6
<data>
<variable
name="repository"
type="me.mladenrakonjac.modernandroidapp.uimodels.Repository"
/>
</data>

我们可以在布局中访问存储的变量 repository , 例如,我们可以如下使用 id 是 repository_name 的 TextView, 如下:

1
android:text="@{repository.repositoryName}"

repository_name 文本视图将显示从 repository 变量的属性 repositoryName 获取的文本。剩下的唯一事情就是将 repository 变量从 xml 绑定到 MainActivity.kt 中的 repository。

点击 Build 使 DataBinding 为我们生成类,然后在 MainActivity 中添加两行代码:

1
2
binding.repository = repository
binding.executePendingBindings()

如果你运行 APP, 你会看到 TextView 上显示的是: “Medium Android Repository Article” , 非常棒的功能,是吧?

但是,如果我们像下面这样改一下呢?

1
Handler().postDelayed({repository.repositoryName="New Name"}, 2000)

新的文本将会在 2000ms 后显示吗?不会的,你必须重新设置一次 repository , 像这样:

1
2
3
Handler().postDelayed({repository.repositoryName="New Name"
binding.repository = repository
binding.executePendingBindings()}, 2000)

但是,如果我们每次更改一个属性都要这么写的话,那就非常蛋疼了,这里有一个更好的方案叫做 Property Observer

让我们首先解释一下什么是观察者模式,因为在 rxJava 部分中我们也将需要它:

可能你已经听说过 http://androidweekly.net/ , 这是一个关于 Android 开发的周刊。如果您想接收它,则必须订阅它并提供您的电子邮件地址。过了一段时间,如果你不想看了,你可以去网站上取消订阅。

这就是一个 观察者/被观察者 的模式,在这个例子中, Android 周刊是 被观察者 ,它每周都会发布新闻通讯。读者是 观察者 ,因为他们订阅了它,一旦订阅就会收到数据,如果不想读了,则可以停止订阅。

Property Observer 在这个例子中就是 xml layout, 它将会监听 Repository 实例的变化。因此, Repository被观察者 ,例如,一旦在 Repository 类的实例中更改了 repository nane 属性后,xml 不调用下面的代码也会更新:

1
2
binding.repository = repository
binding.executePendingBindings()

如何让它使用 Data Binding 库呢?,Data Binding 库提供了一个 BaseObservable 类,我们的 Repostory 类必须继承它。

1
2
3
4
5
6
7
8
9
10
11
class Repository(repositoryName : String, var repositoryOwner: String?, var numberOfStars: Int?
, var hasIssues: Boolean = false) : BaseObservable(){

@get:Bindable
var repositoryName : String = ""
set(value) {
field = value
notifyPropertyChanged(BR.repositoryName)
}

}

当我们使用了 Bindable 注解时,就会自动生成 BR 类。你会看到,一旦设置新值,就会通知它更新。现在运行 app 你将看到仓库的名字在 2 秒后改变而不必再次调用 executePendingBindings()