簡評:目前吟吝,在 Android 開發(fā)中找到一個覆蓋所有的新技術(shù)的項(xiàng)目難如登天篡九,所以作者決定自己寫一個问窃。本文所以使用的技術(shù)包括:
0. Android Studio 3, beta1
1. Kotlin 語言
2. 構(gòu)建變體
3. ConstraintLayout
4. 數(shù)據(jù)綁定庫
5. MVVM 架構(gòu) + 存儲庫模式(使用映射器)+ Android Manager Wrappers(Part 2)
6. RxJava2 及它如何在架構(gòu)中起作用
7. Dagger 2.11万俗,什么是依賴注入康二,為什么需要它
8. 改造(使用 Rx Java2)
9. Room(使用 Rx Java2)
我們的 app 看起來是什么樣的嫡纠?
我們的 app 將會是最簡單的懒叛,將使用所有上面提到的技術(shù)丸冕,只用一個功能:拉取 GitHub 上的所有 google 案例倉庫,把這些數(shù)據(jù)保存到本地?cái)?shù)據(jù)庫并展示給用戶薛窥。
我將盡可能地解釋每一行代碼胖烛。你可以從 github 上跟進(jìn)我提交的代碼。
讓我們一起動手:
0. Android Studio
要安裝 Android Studio 3 beta1(現(xiàn)在已發(fā)布正式版)诅迷,你要進(jìn)入這個頁面佩番。
注意:如果你想要和之前安裝的某個版本共存,在 Mac 上你應(yīng)該在應(yīng)用文件夾中重命名舊的版本竟贯,如“Android Studio Old”答捕。你可以在這里找到更多信息,包括 Windows 和 Linux屑那。
Android Studio 現(xiàn)已支持 Kotlin拱镐。去創(chuàng)建 Android 項(xiàng)目,你會發(fā)現(xiàn)新東西:支持 Kotlin 的標(biāo)簽可選框持际。它是默認(rèn)選中的沃琅。按兩下 next,然后選擇 Empty Activity蜘欲,這樣就完成了益眉。
1. Kotlin
看看 MainActivity.kt:
package me.fleka.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() 意味著我們正在繼承 AppCompatActivity。
此外郭脂,所有的方法都有一個 fun 關(guān)鍵字年碘,在 Kotlin 中你可以不必使用,取決于你的喜好展鸡。你必須使用
override 關(guān)鍵字屿衅,而不是注解。
那么莹弊,savedInstanceState: Bundle? 中的 涤久? 表示什么意思呢?意味著 savedInstanceState 參數(shù)可能是 Bundle 類型或者為 null忍弛。Kotlin 是空安全的語言响迂。如果你定義了:
var a : String
你將得到一個編譯錯誤,因?yàn)?a 必須被初始化细疚,它不能為 null 蔗彤。意味著你必須這樣寫:
var a : String = "Init value"
如果你像下面這樣寫,同樣你將得到一個編譯錯誤:
a = null
要讓 a 成為可空的疯兼,你必須這樣:
var a : String?
為什么這是 Kotlin 語言的一個重要功能呢幕与?因?yàn)樗鼛臀覀儽苊饬?a target="_blank" rel="nofollow">空指針異常。Android 開發(fā)者受夠了空指針異常镇防。即便是 null 的創(chuàng)造者啦鸣,Tony Hoare 先生,也為發(fā)明出 null 而道歉了来氧。假設(shè)我們有一個可空的 nameTextView诫给。以下代碼將會造成 NPE,如果它是 null 的話:
nameTextView.setEnabled(true)
而 Kotlin 將不允許我們做類似這樣的事啦扬。它強(qiáng)制我們使用 ? 或者 !! 操作符中狂,如果我們使用 ? 操作符:
nameTextView?.setEnabled(true)
這行代碼僅當(dāng) nameTextView 不為 null 才會執(zhí)行。換句話說扑毡,如果我們使用了 !! 操作符:
nameTextView!!.setEnabled(true)
如果 nameTextView 為 null胃榕,它將報(bào) NPE。想冒險(xiǎn)的人才會用 :)
這只是有關(guān) Kotlin 的一點(diǎn)小小的介紹瞄摊,隨著我們深入勋又,后面不再介紹其他 Kotlin 特性代碼。
2. 構(gòu)建變體
在開發(fā)中换帜,你通常會有不同的環(huán)境楔壤。最常見的就是測試和生產(chǎn)環(huán)境。這些環(huán)境在服務(wù)器 url惯驼,圖標(biāo)蹲嚣,名字递瑰,目標(biāo) api 上等等有所不同。在 fleka隙畜,我們的每一個項(xiàng)目都要遵守:
- finalProduction, 在 Google Play 商店中發(fā)布
- demoProduction抖部,這個版本有著生產(chǎn)服務(wù)器 url 和新功能,但是不會在 Google Play 商店中上線议惰。我們的客戶會和 Google Play 發(fā)布的版本一起安裝您朽,他們會測試這個版本并給我們反饋。
- demoTesting换淆,和 demoProduction 一樣,但是使用的是測試服務(wù)器 url几颜。
- mock倍试,對于開發(fā)者和設(shè)計(jì)者來說很有用。有時候我們的設(shè)計(jì)準(zhǔn)備好了蛋哭,但是 API 還沒準(zhǔn)備好县习。等待 API 準(zhǔn)備好才進(jìn)行開發(fā)不是一個很好的選擇。這個版本會使用假數(shù)據(jù)谆趾,這樣設(shè)計(jì)團(tuán)隊(duì)就可以測試它躁愿,并給予我們反饋。一旦 API 準(zhǔn)備好了沪蓬,我們就會切換到 demoTesting 環(huán)境彤钟。
在這個應(yīng)用中,我們將會用上述所有的環(huán)境跷叉。它們有不同的名字和 applicationId逸雹。在 gradle 3.0.0 中有一個新的 api 叫 flavorDimension,允許你混合不同的開發(fā)環(huán)境云挟,這樣你可以混合 demo 和 minApi23梆砸。在我們的 app 中,我們將使用默認(rèn)的 flavorDimension园欣。打開 build.gradle帖世,然后在 android{} 中插入下面的代碼:
flavorDimensions "default"
productFlavors {
finalProduction {
dimension "default"
applicationId "me.fleka.modernandroidapp"
resValue "string", "app_name", "Modern App"
}
demoProduction {
dimension "default"
applicationId "me.fleka.modernandroidapp.demoproduction"
resValue "string", "app_name", "Modern App Demo P"
}
demoTesting {
dimension "default"
applicationId "me.fleka.modernandroidapp.demotesting"
resValue "string", "app_name", "Modern App Demo T"
}
mock {
dimension "default"
applicationId "me.fleka.modernandroidapp.mock"
resValue "string", "app_name", "Modern App Mock"
}
}
打開 string.xml,刪除 app_name 字符串沸枯,這樣就沒有沖突了日矫。然后點(diǎn)擊 Sync。如果你打開Build Variants 界面绑榴,你會看到四種不同的變體搬男,每個都有兩種構(gòu)建類型:Debug 和 Release。切換到demoProduction彭沼,然后運(yùn)行缔逛,接著切換到另一個,然后運(yùn)行。你應(yīng)該會看到兩個不同名字的應(yīng)用褐奴。
3. ConstraintLayout
如果你打開 activity_main.xml按脚,你應(yīng)該會看到 ConstrainLayout 布局。如果你寫過 iOS 應(yīng)用敦冬,你應(yīng)該知道 AutoLayout辅搬。ConstrainsLayout 和它非常相似。它們甚至使用了相同的 Cassowary 算法脖旱。
<?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.fleka.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" />
</android.support.constraint.ConstraintLayout>
約束幫助我們描述視圖之間的關(guān)系堪遂。每個視圖都有 4 個約束,每邊一個萌庆。上面的代碼中溶褪,我們的視圖每一邊都被約束到父視圖。
如果你在 Design 選項(xiàng)卡中把 Hello World 文本視圖往上挪動一點(diǎn)點(diǎn)践险,在 Text 選項(xiàng)卡中會出現(xiàn)一行新代碼:
app:layout_constraintVertical_bias="0.28"
Design 和 Text 選項(xiàng)卡是同步的猿妈。我們在 Design 上的移動影響了 Text 選項(xiàng)卡中的 xml,反之亦然巍虫。垂直偏差描述了視圖在它的約束中的垂直的趨勢彭则。如果你想要視圖垂直居中,你應(yīng)該使用:
app:layout_constraintVertical_bias="0.28"
讓我們的 Activity 僅僅顯示一個倉庫占遥。它將會有一個倉庫名俯抖,關(guān)注數(shù),擁有者以及會顯示倉庫有沒有問題瓦胎。
要獲得這樣的布局蚌成,xml 是這樣的:
<?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.fleka.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>
不要因?yàn)?tools:text 而困惑,它僅僅是讓我們的布局預(yù)覽更好看凛捏。
我們可以注意到我們的布局是扁平的担忧。沒有嵌套的布局。你應(yīng)該盡可能地避免使用嵌套的布局坯癣,因?yàn)樗鼤绊懶阅芷渴ⅰ?梢栽?a target="_blank" rel="nofollow">這里找到更多信息示罗。同樣的惩猫,ConstraintLayout 在不同的屏幕尺寸上也能很好的工作。
這樣一來蚜点,可以相當(dāng)快地得到我想要的界面轧房。這就是 ConstraintLayout 的一些小介紹。你可以在 Google 代碼實(shí)驗(yàn)室中找到绍绘,在 github 中也有關(guān)于ConstraintLayout 的文檔奶镶。
4. 數(shù)據(jù)綁定庫
當(dāng)我聽說數(shù)據(jù)綁定庫時迟赃,我問我自己的第一件事就是,我為什么要用 Butterknife 厂镇?而在我學(xué)習(xí)了更多數(shù)據(jù)綁定的知識后纤壁,我發(fā)現(xiàn)它真的非常好用。
- ButterKnife 可以幫到我們什么捺信?
ButterKnife 幫助我們擺脫枯燥的 findViewById酌媒。如果你有 5 個視圖,沒有 ButterKnife迄靠,你會有 5 + 5 行代碼來綁定你的視圖秒咨。用了 ButterKnife,你只需要用 5 行代碼掌挚。
- ButterKnife 的缺點(diǎn)是什么雨席?
ButterKnife 依舊沒有解決維護(hù)代碼的問題。當(dāng)我使用 ButterKnife 時疫诽,經(jīng)常得到一個運(yùn)行時異常,因?yàn)槲以?xml 中刪除了一個視圖旦委,且在 activity/fragment 中忘了刪除綁定代碼奇徒。同樣地,當(dāng)你在 xml 中添加了一個視圖缨硝,你必須重新綁定一次摩钙。這相當(dāng)麻煩。你在維護(hù)綁定時浪費(fèi)了時間查辩。
- 什么是數(shù)據(jù)綁定庫胖笛?
使用數(shù)據(jù)綁定庫,你只需要用一行代碼就可以綁定你的視圖宜岛!接下來展示一下它是如何工作的长踊。首先添加依賴:
// 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"
}
注意:上面的數(shù)據(jù)綁定庫的編譯器和你的項(xiàng)目的 build.gradle 中的 gradle 版本需一致:
classpath 'com.android.tools.build:gradle:3.0.0-beta1'
現(xiàn)在點(diǎn)擊 Sync 按鈕。打開 activity_main.xml 然后用 layout 標(biāo)簽包裹住 ConstraintLayout:
<?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.fleka.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>
把所有的 xmlns 移動到 layout 標(biāo)簽萍倡。然后點(diǎn)擊 Build 按鈕身弊,或者使用快捷鍵 Cmd + F9. 我們需要構(gòu)建項(xiàng)目,這樣數(shù)據(jù)綁定庫能夠生成 ActivityMainBinding 類列敲,我們將在 MainActivity 中使用它阱佛。
如果你不構(gòu)建項(xiàng)目,那么你看不到 ActivityMainBinding 類戴而,因?yàn)樗窃诰幾g時生成的凑术。我們還沒有完成綁定,我們只是定義了一個非空的 ActivityMainBinding 類型的變量所意。你會注意到我沒有把 ? 放在ActivityMainBinding 的后面淮逊,而且也沒有初始化它催首。這怎么可能?
lateinit 關(guān)鍵字允許我們使用非空的等待被初始化的變量壮莹。和 ButterKnife 類似翅帜,初始化綁定需要在 onCreate 方法中進(jìn)行,在我們的布局準(zhǔn)備完成后命满。此外涝滴,你不應(yīng)該在 onCreate 方法中聲明綁定,因?yàn)槟愫苡锌赡茉?onCreate 方法外使用它胶台。我們的 binding 不能為空歼疮,所以這就是我們使用 lateinit 的原因。使用 lateinit 修飾诈唬,我們不需要在每次訪問它的時候檢查 binding 變量是否為空韩脏。
讓我們來初始化我們的 binding 變量,你應(yīng)該把這句:
setContentView(R.layout.activity_main)
替換成:
binding = DataBindingUtil.setContentView(this, R.layout.activity_main)
就這樣铸磅!你成功地綁定了自己的視圖∩氖福現(xiàn)在你可以訪問它并做一些改動。例如阅仔,讓我們把倉庫的名字改為“Modern Android Medium Article”:
binding.repositoryName.text = "Modern Android Medium Article"
你可以看到我們可以通過 binding 變量來訪問 activity_main.xml 中的所有視圖(當(dāng)然是有 id 的那些)吹散。這就是為什么數(shù)據(jù)綁定比 ButterKnife 更好的原因。
5. Kotlin 中的 Getters 和 setters
可能你已經(jīng)注意到了八酒,Kotlin 沒有像 Java 中的 .setText() 方法空民。我會在這里解釋一下與 Java 相比,Kotlin 中的 getters 和 setters 是如何工作的羞迷。
首先界轩,你應(yīng)該知道為什么我們要用 setters 和 getters。我們用它來隱藏類中的變量衔瓮,僅允許使用方法來訪問這些變量浊猾,這樣我們就可以向用戶隱藏類中的細(xì)節(jié),并禁止用戶直接修改我們的類热鞍。假設(shè)我們用 Java 寫了一個 Square 類:
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() 方法与殃,我們禁止用戶把 a 設(shè)置為負(fù)數(shù),因?yàn)檎叫蔚倪叢粸樨?fù)數(shù)碍现。我們把 a 設(shè)置為 private幅疼,這樣它就不能直接被設(shè)置。同樣意味著我們這個類的用戶不能直接地拿到 a昼接,所以我們提供了 getter爽篷。getter 返回 a。如果你有 10 個變量慢睡,類似地逐工,你要提供 10 個 getters铡溪。寫這些不經(jīng)思考的代碼很無聊。
Kotlin 讓我們開發(fā)者的生活更加簡單泪喊,如果你調(diào)用:
var side: Int = square.a
這并不意味著你直接地訪問 a棕硫,而是類似這樣的:
int side = square.getA();
Kotlin 自動生成默認(rèn)的 getter 和 setter,除非你需要特殊的 setter 和 getter袒啼,你需要定義它們 :
var a = 1
set(value) { field = Math.abs(value) }
field ? 這又是什么哈扮?為了看起來更清楚,我們來看看下面的代碼:
var a = 1
set(value) { a = Math.abs(value) }
這意味著你你在 set 方法中調(diào)用了 set 方法蚓再,因?yàn)樵?Kotlin 中滑肉,你不能直接訪問屬性。這會造成無窮遞歸摘仅,當(dāng)你調(diào)用 a = something 時靶庙,它自動調(diào)用了 set 方法。現(xiàn)在你應(yīng)該知道為什么要使用 field 關(guān)鍵字了娃属。
回到我們的代碼六荒,我將向你展示 Kotlin 語言中更棒的功能:apply:
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 = "Fleka"
numberOfStarts.text = "1000 stars"
}
}
}
apply 允許你調(diào)用一個實(shí)例的多個方法。我們還沒有完成數(shù)據(jù)綁定矾端,還有更棒的事情掏击。讓我們先為倉庫(這是 GitHub 倉庫的 UI 模型類,存放了我們要展示的數(shù)據(jù)须床,別和倉庫模式搞混了)定義一個 ui 模型類铐料。點(diǎn)擊 New -> Kotlin File/Class 來 創(chuàng)建 Kotlin 類:
class Repository(var repositoryName: String?,var repositoryOwner: String?,var numberOfStars: Int? ,var hasIssues: Boolean = false)
在 Kotlin 中渐裂,首要構(gòu)造函數(shù)是類的頭部的一部分豺旬。如果你不提供第二個構(gòu)造函數(shù),這樣就行了柒凉,你創(chuàng)建類的工作完成了族阅。沒有構(gòu)造函數(shù)參數(shù)賦值,也沒有 getter 和 setter膝捞,全部的類只用一行代碼坦刀!
回到 MainActivity.kt,創(chuàng)建一個 Repository 類的實(shí)例:
var repository = Repository("Medium Android Repository Article",
"Fleka", 1000, true)
可以看到蔬咬,在對象創(chuàng)建中沒有 new 關(guān)鍵字±鹨#現(xiàn)在打開 activity_main.xml,然后添加一個 data 標(biāo)簽:
<data>
<variable
name="repository"
type="me.fleka.modernandroidapp.uimodels.Repository"
/>
</data>
我們可以在 layout 中訪問我們的 Repository 類型的 repository 變量林艘。例如盖奈,我們可以在 TextView 中使用repositoryName:
android:text="@{repository.repositoryName}"
這個 TextView 將會展示從 repository 變量中得到的 repositoryName 屬性。最后剩下的就是綁定 xml 中的repository 和 MainActivity.kt 中的repository 變量狐援。點(diǎn)擊 Build 按鈕钢坦,讓數(shù)據(jù)綁定庫生成所需的類究孕,然后回到 MainActivity 添加下面的代碼:
binding.repository = repository
binding.executePendingBindings()
如果你運(yùn)行 app,你會看到 TextView 展示 “Medium Android Repository Article”爹凹。很棒的功能厨诸,對吧?:)
但如果我們這樣做:
Handler().postDelayed({repository.repositoryName="New Name"}, 2000)
新的文本會在 2000 毫秒后顯示出來嗎禾酱?并不會微酬。你需要重新設(shè)置 repository。像這樣:
Handler().postDelayed({repository.repositoryName="New Name"
binding.repository = repository
binding.executePendingBindings()}, 2000)
如果我們每次都這樣做就非常無趣了宇植,有一個更好的解決方案叫屬性觀察者得封。讓我們先來描述一下什么是觀察者模式,因?yàn)槲覀冊?RxJava 章節(jié)中需要它指郁。
可能你已經(jīng)聽說過 androidweekly 忙上。它是個關(guān)于 Android 開發(fā)的每周時事資訊。當(dāng)你想收到資訊闲坎,你需要在給定的郵箱地址中訂閱它疫粥。一段時間后,你可能決定取消訂閱腰懂。
這就是一個觀察者/可觀察的模式的例子梗逮。這個例子中,Android Weekly 是可觀察的绣溜,它每周放出資訊慷彤,讀者是觀察者,因?yàn)樗麄冊谏厦嬗嗛喠瞬烙鳎却沦Y訊發(fā)送底哗,一旦他們收到了,他們就可以閱讀锚沸。如果某些人不喜歡跋选,他/她就可以停止監(jiān)聽。
我們所用的屬性觀察者就是 xml 布局哗蜈,他們會監(jiān)聽 Repository 實(shí)例的變化前标。所以,Repository 是可觀察的距潘。例如炼列,一旦 Repository 實(shí)例的倉庫名字這個屬性變化了,xml 就能夠更新而不必調(diào)用:
binding.repository = repository
binding.executePendingBindings()
怎樣才能做到音比?數(shù)據(jù)綁定庫給我們提供了 BaseObservable 類俭尖,Repository 類應(yīng)該實(shí)現(xiàn)這個類:
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 類硅确。你會看到目溉,一旦新的值設(shè)置后明肮,我們就通知它。現(xiàn)在運(yùn)行 app 你將看到倉庫的名字在 2 秒后改變而不必再次調(diào)用executePendingBindings()缭付。
英文原文:Modern Android development with Kotlin (September 2017) Part 1
舊文推薦:
Kotlin 讓使用 Android API 變得輕松
“Effective Java” 可能對 Kotlin 的設(shè)計(jì)造成了怎樣的影響——第一部分