Plaid
庫(kù)是 google
之前的一個(gè) demo
庫(kù)耿币,近期利用 kotlin
進(jìn)行了重寫.
某種程度上梳杏,是 Kotlin
和 Jetpack
的一個(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ō):
-
Plaid
項(xiàng)目劃分 -
Plaid
的代碼結(jié)構(gòu) -
Plaid
的代碼實(shí)現(xiàn) -coroutines
協(xié)程實(shí)現(xiàn)
1. Plaid
項(xiàng)目劃分
Plaid
模塊化結(jié)構(gòu)圖:
屬于多模塊化的設(shè)計(jì)淹接, core
是繼承模塊十性,其他模塊是業(yè)務(wù)模塊。
2. Plaid
每個(gè)模塊代碼設(shè)計(jì)結(jié)構(gòu):
官方的設(shè)計(jì)類圖如下:
分為三層:
-
UI
層 -
Domain
層 -
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)自哪。
比如代碼中的:UserRepository
和 UserRemoteDataSource
.
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
的形式輸出給 Activity
「View
層」。
LiveData
是ViewModel
對(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ù)的原因之一,它使用了 kotlin
和 Jetpack
實(shí)現(xiàn)霹抛。
kotlin
搓逾,當(dāng)然這里使用 coroutine
實(shí)現(xiàn)。
Jetpack
杯拐,使用了 LiveData
, Room
, Data Binding
使用前提:引入?yún)f(xié)程庫(kù)霞篡。
代碼
- 首先在
View
層的Activity
或者Fragment
中獲取到ViewModel
; - 手動(dòng)調(diào)用
ViewModel.getXXX()
去獲取數(shù)據(jù) - 對(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)用路徑為:
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
的作用:
- 做一些緩存,減少不必要的接口再次訪問(wèn)准谚;
- 處理一下數(shù)據(jù)挫剑,精簡(jiǎn)邏輯和數(shù)據(jù),
dataSource
返回的數(shù)據(jù)柱衔,需要經(jīng)過(guò)它的處理再返回給ViewModel
- 數(shù)據(jù)來(lái)源為兩方面
local
和remote
樊破,需要經(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-core
和org.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
的銷毀