Plaid 開(kāi)源庫(kù)學(xué)習(xí)

Plaid 庫(kù)是 google 之前的一個(gè) demo 庫(kù)耿币,近期利用 kotlin 進(jìn)行了重寫.

某種程度上梳杏,是 KotlinJetpack 的一個(gè)實(shí)踐。

github 地址 :https://github.com/android/plaid
https://github.com/android/plaid
https://mp.weixin.qq.com/s/9FyPM-VjgMwZErmEtbj2uQ

以下內(nèi)容從三個(gè)方面來(lái)說(shuō):

  1. Plaid 項(xiàng)目劃分
  2. Plaid 的代碼結(jié)構(gòu)
  3. Plaid 的代碼實(shí)現(xiàn) - coroutines 協(xié)程實(shí)現(xiàn)

1. Plaid 項(xiàng)目劃分

Plaid 模塊化結(jié)構(gòu)圖:

plaid 代碼結(jié)構(gòu)模塊化圖

屬于多模塊化的設(shè)計(jì)淹接, core 是繼承模塊十性,其他模塊是業(yè)務(wù)模塊。

2. Plaid 每個(gè)模塊代碼設(shè)計(jì)結(jié)構(gòu):

官方的設(shè)計(jì)類圖如下:

plaid 設(shè)計(jì)類圖

圖片來(lái)自:https://mp.weixin.qq.com/s/9FyPM-VjgMwZErmEtbj2uQ

分為三層:

  1. UI
  2. Domain
  3. Data

可簡(jiǎn)單看作 MVP 的一種延伸塑悼。

2.1 Data 層 -> model

根據(jù)數(shù)據(jù)來(lái)源分為兩部分劲适,本地?cái)?shù)據(jù)LocalDataSource 和 網(wǎng)絡(luò)接口數(shù)據(jù) RemoteDataSource.

其他層次不關(guān)系 data 數(shù)據(jù)內(nèi)部數(shù)據(jù)是來(lái)自哪,所以厢蒜,在 data 層里面有個(gè) Repository 類霞势,外部只需要去 Repository 獲取數(shù)據(jù)和存儲(chǔ)數(shù)據(jù)烹植,而不關(guān)心數(shù)據(jù)來(lái)自哪。

比如代碼中的:UserRepositoryUserRemoteDataSource.

Repository 中可以實(shí)現(xiàn)一部分的數(shù)據(jù)緩存支示,避免不必要的流量浪費(fèi)和用戶體驗(yàn)刊橘。

2. 2Domain

presenter

在這里使用了 UseCase 這個(gè)概念。
實(shí)際上是把一些小型的輕量級(jí)并且可以復(fù)用的邏輯單獨(dú)放入一個(gè)類「UseCase」里面颂鸿,
這些類將基于實(shí)際的業(yè)務(wù)邏輯開(kāi)處理數(shù)據(jù)促绵。
比如說(shuō)回復(fù)評(píng)論,獲取回答等單獨(dú)的任務(wù)嘴纺。
例如:獲取回答列表败晴,有太多地方在使用這個(gè)接口去獲取, 查找問(wèn)題時(shí)也不是很方便栽渴,如果統(tǒng)一尖坤,確實(shí)會(huì)有些幫助
例如:PostReplyUseCase

個(gè)人理解:弱化了 ViewModel 的作用,把一些在 ViewModel 里面處理的邏輯劃分給了 UseCase闲擦。
現(xiàn)在 ViewModel 只負(fù)責(zé)拿到數(shù)據(jù)后的 UI 邏輯處理.
這也是為什么在上面官方給出的圖中慢味,把 ViewModel 劃分在 UI 層的一個(gè)原因。

2.3 UI

在這個(gè)設(shè)計(jì)中墅冷,包含了 View 層「Activity, fragment, xml」和 Presenter 邏輯層「ViewModel 被弱化了」纯路。

在這一層中,ViewModel主要是為了 UI 提供數(shù)據(jù)并根據(jù)「用戶操作觸發(fā)不同的邏輯執(zhí)行」寞忿, 依賴著 UseCase 去獲取數(shù)據(jù)驰唬,然后把數(shù)據(jù)通過(guò) LiveData 的形式輸出給 ActivityView 層」。

LiveDataViewModel 對(duì)外部輸出的唯一數(shù)據(jù)腔彰。

2.4 總結(jié)以下代碼結(jié)構(gòu)上的邏輯

由上面的可得到叫编, 代碼執(zhí)行的邏輯是:


Activity->ViewModel: 執(zhí)行某個(gè)邏輯
ViewModel->XXXUseCase: 執(zhí)行某個(gè)復(fù)雜邏輯
XXXUseCase->XXXRepository: 去 data 中拿取數(shù)據(jù)
XXXRepository->XXXDataSource: 真正拿數(shù)據(jù)的地方
XXXRepository-->XXXUseCase: 在 UseCase 中處理一下
XXXUseCase-->ViewModel: 返回?cái)?shù)據(jù)給 viewModel
ViewModel-->Activity: liveData 反饋給 Activity
代碼流程圖

3. 從代碼層面看一看

想要分享這個(gè)庫(kù)的原因之一,它使用了 kotlinJetpack 實(shí)現(xiàn)霹抛。

kotlin 搓逾,當(dāng)然這里使用 coroutine 實(shí)現(xiàn)。
Jetpack 杯拐,使用了 LiveData, Room , Data Binding

使用前提:引入?yún)f(xié)程庫(kù)霞篡。

代碼

  1. 首先在 View 層的 Activity或者 Fragment 中獲取到 ViewModel;
  2. 手動(dòng)調(diào)用 ViewModel.getXXX() 去獲取數(shù)據(jù)
  3. 對(duì)一些需要的數(shù)據(jù)利用 LiveData 觀察變化,而獲取數(shù)據(jù)和做 UI 改變

下面看一些具體的代碼實(shí)現(xiàn):

3.1 獲取到 ViewModel

Plaid 中使用的是 Dragger 實(shí)現(xiàn)注入的藕施。

代碼大致如下:

Provides
fun provideLoginViewModel(
    factory: DesignerNewsViewModelFactory
): LoginViewModel =
    ViewModelProviders.of(activity, factory).get(LoginViewModel::class.java)

上述代碼省去了 Inject 的注入過(guò)程寇损。

嗯……因?yàn)閭€(gè)人原因凸郑,不太喜歡使用 Dragger.

Activity 中觀察 liveData 代碼:

// 在 activity 中的 observer
viewModel.uiState.observe(this, Observer {
    val uiModel = it ?: return@Observer
    // balabala 的 UI 上的操作
    ....
})
3.2 ViewModel 發(fā)起數(shù)據(jù)請(qǐng)求

代碼示例如下:

// 在 ViewModel 代碼中
private fun getComments() = viewModelScope.launch(dispatcherProvider.computation) {
    val result = getCommentsWithRepliesAndUsers(story.links.comments)
    if (result is Result.Success) {
        // 切換到主線程
         withContext(dispatcherProvider.main) { 
             //通過(guò) liveData 拋給 Activity 的 observer
             emitUiModel(result.data) 
         }
    }
}

代碼中 viewModelScope 來(lái)自 liftcycle-viewmodel-ktx-2.2.0 裳食,是 ViewModel 的一個(gè)擴(kuò)展屬性,源碼如下:

/**
 * [CoroutineScope] tied to this [ViewModel].
 * This scope will be canceled when ViewModel will be cleared, it.e [ViewModel.onCleared] is called
 *
 * This scope is bound to [Dispatchers.Main]
 */
val ViewModel.viewModelScope: CoroutineScope
        get() {
            val scope: CoroutineScope? = this.getTag(JOB_KEY)
            if (scope != null) {
                return scope
            }
            return setTagIfAbsent(JOB_KEY,
                CloseableCoroutineScope(SupervisorJob() + Dispatchers.Main))
        }

返回的是一個(gè) CloseableCoroutineScope.

同時(shí)芙沥,這里會(huì)有一個(gè) setTagIfAbsent(xxx)mBagOfTags 這里存儲(chǔ)了 CloseableCoroutineScope 的實(shí)例 诲祸,會(huì)在 ViewModel 被銷毀時(shí)回收掉浊吏。
參考 viewModelScope 的銷毀

3.3 UseCase 調(diào)度接口

上述代碼中的 getCommentsWithRepliesAndUsers 其實(shí)是 GetCommentsWithRepliesAndUsersUseCase 的一個(gè)實(shí)例, 最終救氯,在這里調(diào)用的方法為:

// get the users
val usersResult = userRepository.getUsers(userIds)

調(diào)用路徑為:


代碼2
3.4 Repository 的實(shí)現(xiàn)

其實(shí)這一層的需要不需要找田,完全看開(kāi)發(fā)。

在這個(gè)例子中 UserRepository 的實(shí)現(xiàn)着憨,里面有一個(gè)成員變量 cachedUsers, 用做緩存墩衙,減少不必要的網(wǎng)絡(luò)訪問(wèn)。一些需求是不需要這樣的邏輯的甲抖,可完全拋棄掉 Repository漆改。

class UserRepository(private val dataSource: UserRemoteDataSource) {
    private val cachedUsers = mutableMapOf<Long, User>()
    
    suspend fun getUsers(ids: Set<Long>): Result<Set<User>> {
        ...
    }

}

Repository 的作用:

  1. 做一些緩存,減少不必要的接口再次訪問(wèn)准谚;
  2. 處理一下數(shù)據(jù)挫剑,精簡(jiǎn)邏輯和數(shù)據(jù),dataSource 返回的數(shù)據(jù)柱衔,需要經(jīng)過(guò)它的處理再返回給 ViewModel
  3. 數(shù)據(jù)來(lái)源為兩方面 localremote 樊破,需要經(jīng)過(guò) Repository 的合并或者篩選再返回給 ViewModel
3.5 DataSource 的實(shí)現(xiàn)

往往我們會(huì)認(rèn)為 DataSource 是來(lái)自網(wǎng)絡(luò)的,而忽視了本地的數(shù)據(jù)唆铐,所以應(yīng)該把 DataSource 分為兩類哲戚,一種是 local 數(shù)據(jù),一種是 remote 數(shù)據(jù)或链。

代碼實(shí)現(xiàn):

// safeApiCall() 是一個(gè)高階函數(shù)惫恼,本質(zhì)上是做了 try catch 操作「最小程度代碼塊的 try catch」
suspend fun getUsers(userIds: List<Long>) = safeApiCall(
    call = { requestGetUsers(userIds) },
    errorMessage = "Error getting user"
)
//請(qǐng)求數(shù)據(jù)
private suspend fun requestGetUsers(userIds: List<Long>): Result<List<User>>{
    ....
    service.getUser(userIds)
    ...
}

一定要讓 DataSource 盡可能純粹,它只負(fù)責(zé)請(qǐng)求數(shù)據(jù)澳盐,返回?cái)?shù)據(jù)祈纯,而不對(duì)數(shù)據(jù)進(jìn)行處理。

對(duì)于 safeApiCall()Result 的實(shí)現(xiàn)叼耙,感興趣的可以私下看一看腕窥。

總結(jié)

其實(shí)在這部分代碼中,很多 kotlin 的小細(xì)節(jié)都值得學(xué)習(xí)筛婉,因?yàn)樘^(guò)詳細(xì)簇爆,這里不再介紹,真心推薦一下爽撒,源碼還是不錯(cuò)的入蛆,雖然使用了 Dragger ,在閱讀體驗(yàn)上并不是很好硕勿,但還是特別值得學(xué)習(xí)的一個(gè)代碼哨毁。

當(dāng)然上面是個(gè)人的一些淺顯理解,有錯(cuò)誤的地方還請(qǐng)指出源武。

版本號(hào)參考:

lifecycle-viewmodel 版本號(hào):2.2.0
lifecycle-viewmodel-ktx 版本號(hào):2.2.0

使用 coroutines 要求

  • 引入 org.jetbrains.kotlinx:kotlinx-coroutines-coreorg.jetbrains.kotlinx:kotlinx-coroutines-android
  • 引入 retrofit
    - 2.6.0 以下版本扼褪,需要使用 https://github.com/JakeWharton/retrofit2-kotlin-coroutines-adapter 兼容想幻;
    - 2.6.0 以上版本,不需要兼容, 支持 suspend

參考鏈接:
https://juejin.im/post/5d5f80836fb9a06b2548ee47
https://www.kotlincn.net/docs/reference/coroutines/coroutines-guide.html
viewModelScope 的銷毀

https://mp.weixin.qq.com/s/d9lx8iSGRabeuuYYVz-z1Q

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末话浇,一起剝皮案震驚了整個(gè)濱河市脏毯,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌幔崖,老刑警劉巖食店,帶你破解...
    沈念sama閱讀 221,198評(píng)論 6 514
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異赏寇,居然都是意外死亡叛买,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,334評(píng)論 3 398
  • 文/潘曉璐 我一進(jìn)店門蹋订,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)率挣,“玉大人,你說(shuō)我怎么就攤上這事露戒〗饭Γ” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 167,643評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵智什,是天一觀的道長(zhǎng)动漾。 經(jīng)常有香客問(wèn)我,道長(zhǎng)荠锭,這世上最難降的妖魔是什么旱眯? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 59,495評(píng)論 1 296
  • 正文 為了忘掉前任,我火速辦了婚禮证九,結(jié)果婚禮上删豺,老公的妹妹穿的比我還像新娘。我一直安慰自己愧怜,他們只是感情好呀页,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,502評(píng)論 6 397
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著拥坛,像睡著了一般蓬蝶。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上猜惋,一...
    開(kāi)封第一講書(shū)人閱讀 52,156評(píng)論 1 308
  • 那天丸氛,我揣著相機(jī)與錄音,去河邊找鬼著摔。 笑死缓窜,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播雹洗,決...
    沈念sama閱讀 40,743評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼卧波!你這毒婦竟也來(lái)了时肿?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 39,659評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤港粱,失蹤者是張志新(化名)和其女友劉穎螃成,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體查坪,經(jīng)...
    沈念sama閱讀 46,200評(píng)論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡寸宏,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,282評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了偿曙。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片氮凝。...
    茶點(diǎn)故事閱讀 40,424評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖望忆,靈堂內(nèi)的尸體忽然破棺而出罩阵,到底是詐尸還是另有隱情,我是刑警寧澤启摄,帶...
    沈念sama閱讀 36,107評(píng)論 5 349
  • 正文 年R本政府宣布稿壁,位于F島的核電站,受9級(jí)特大地震影響歉备,放射性物質(zhì)發(fā)生泄漏傅是。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,789評(píng)論 3 333
  • 文/蒙蒙 一蕾羊、第九天 我趴在偏房一處隱蔽的房頂上張望喧笔。 院中可真熱鬧,春花似錦龟再、人聲如沸溃斋。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 32,264評(píng)論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)梗劫。三九已至,卻和暖如春截碴,著一層夾襖步出監(jiān)牢的瞬間梳侨,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,390評(píng)論 1 271
  • 我被黑心中介騙來(lái)泰國(guó)打工日丹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留走哺,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,798評(píng)論 3 376
  • 正文 我出身青樓哲虾,卻偏偏與公主長(zhǎng)得像丙躏,于是被迫代替她去往敵國(guó)和親择示。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,435評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容