繼上次分享KtArmor的基礎使用方法, 在網絡請求邏輯上,在調用上,總感受不夠優雅直觀
,嵌套過深
的問題,這樣使得代碼看起來臃腫,不美觀。因此在這篇中,分享一下我在網絡請求調用
方面的 封裝之路
。但願你們喜歡~java
在演示實例過程,我才用採用的是 玩Android 提供的接口 API。 框架方面,我採用的是 Retrofit
+ OkHttp
+ Coroutine
,示例演示是以 Kotlin
+ MVP
架構。若有不瞭解的同窗,能夠應當先去學習相關框架
,否則觀看效果不佳。android
class LoginActivity : MvpActivity<LoginContract.Presenter>(), LoginContract.View {
... 省略部分代碼
override fun initListener() {
super.initListener()
mBtnLogin.setOnClickListener {
presenter.login(mEtAccount.str(), mEtPassword.str())
}
}
... 省略部分代碼
}
複製代碼
以上是部分節選代碼。這裏我以登陸功能
爲例,通常狀況下在 LoginActivity 調用 Presenter 層發起 login
請求。git
class LoginPresenter(view: LoginContract.View) : BasePresenter<LoginContract.View>(view),
LoginContract.Presenter {
val presenterScope: CoroutineScope by lazy {
CoroutineScope(Dispatchers.Main + Job())
}
override fun login(account: String, password: String) {
... 省略部分代碼
// 啓動一個 ui Coroutine
presenterScope.launch {
tryCatch({
view?.showLoading()
val response = LoginModel.login(account, password)
if (response.isSuccess()) {
response.data?.let { view?.loginSuc(it) }
} else {
view?.loginFail(response.errorMsg)
}
}, {
view?.loginError(it.toString())
})
}
... 省略部分代碼
}
}
複製代碼
object LoginModel : BaseModel() {
suspend fun login(account: String, password: String): BaseResponse<LoginRsp> {
return launchIO { ApiManager.apiService.loginAsync(account, password).await() }
}
}
複製代碼
在Presenter 層,啓動一個ui Coroutine協程發起 login 請求,model 層就是簡單調用 apiService 發起網絡請求,而後根據response 判斷是否 success 來調用對於 view 的邏輯。這是相對的 「直觀」
, 調用方式。 這裏存在重複的代碼 loginFail
這裏 「通常」 都是顯示一個 toast 提示用戶相關信息。 代碼上看起來臃腫,接下來是把這些步驟封裝成Kt 擴展函數
github
fun launchUI(block: suspend CoroutineScope.() -> Unit, error: ((Throwable) -> Unit)? = null) {
presenterScope.launch {
tryCatch({
block()
}, {
error?.invoke(it) ?: showException(it.toString())
})
}
}
複製代碼
咱們先抽取一個 launchUI 方法到 Presenter 基類中,封裝 Coroutine 啓動
方式,方便管理 coroutine。 並添加一個 error Block 默認方法, 若是沒有傳入 error 參數,默認顯示 log (showException
)api
fun <R> KResponse<R>.execute(success: ((R?) -> Unit)?, error: ((String) -> Unit)? = null) {
if (this.isSuccess()) {
success?.invoke(this.getKData())
} else {
error?.invoke(this.getKMessage()) ?: showError(this.getKMessage())
}
}
複製代碼
而後添加一個 Response 擴展, 處理網絡請求的邏輯。這裏也是添加兩個參數,一個 success,一個 error
(可選參數,默認顯示 toast)。下面是 替換成功擴展方法後代碼。網絡
class LoginPresenter(view: LoginContract.View) : BasePresenter<LoginContract.View>(view),
LoginContract.Presenter {
val presenterScope: CoroutineScope by lazy {
CoroutineScope(Dispatchers.Main + Job())
}
override fun login(account: String, password: String) {
... 省略部分代碼
// 啓動一個 ui Coroutine
launchUI({
view?.showLoading()
LoginModel.login(account, password).execute({ loginRsp ->
loginRsp?.let { view?.loginSuc(it) }
}, {
// TODO loginFail
})
}, {
// TODO loginError
})
// 省略 TODO 後
launchUI({
view?.showLoading()
LoginModel.login(account, password).execute({ loginRsp ->
loginRsp?.let { view?.loginSuc(it) }
})
})
... 省略部分代碼
}
}
複製代碼
以上是抽取成 一個 擴展函數,簡化了 Presenter 處理邏輯。TODO
標識的默認是能夠省略,默認是 toast 一個 message。這樣調用相對「清晰」
一些。但若是 遇到邏輯複雜的話,會存在 嵌套過深的狀況。最近學習了 DSL
(domain-specific language 領域特定語言),引入 DSL 方式優化這些過程。Perfect
!!架構
fun <R> quickLaunch(block: Execute<R>.() -> Unit) {
Execute<R>().apply(block)
}
inner class Execute<R> {
private var successBlock: ((R?) -> Unit)? = null
private var failBlock: ((String?) -> Unit)? = null
private var exceptionBlock: ((Throwable?) -> Unit)? = null
fun request(block: suspend CoroutineScope.() -> KResponse<R>?) {
// LoginModel.login(account, password)
launchUI({
block()?.execute(successBlock, failBlock)
}, exceptionBlock)
}
fun onSuccess(block: (R?) -> Unit) {
// loginRsp?.let { view?.loginSuc(it) }
this.successBlock = block
}
fun onFail(block: (String?) -> Unit) {
// message?.let { view?.loginFail(it) }
this.failBlock = block
}
fun onException(block: (Throwable?) -> Unit) {
// throwable?.let { view?.loginError(it.toString()) }
this.exceptionBlock = block
}
}
複製代碼
DSL看起來比較抽象,在 Presenter 基類裏,建立一個 內部類 Execute,聲明對應的方法(request, onSuccess, onFail, onException
), (在方法下面註釋就是外面 Presenter 傳入的 block,便於理解)。先存儲對應 block,而後在 request 方法統一處理,具體邏輯和擴展差很少,這裏就很少贅述了。咱們看看封裝成 DSL 後效果。app
quickLaunch<LoginRsp> {
request { LoginModel.login(account, password) }
onSuccess { loginRsp ->
loginRsp?.let { view?.loginSuc(it) }
}
onFail { message ->
message?.let { view?.loginFail(it) }
}
onException { throwable ->
throwable?.let { view?.loginError(it.toString()) }
}
}
複製代碼
最終效果是否是很「優雅」
,減小了層級嵌套
,從上而下,直觀明瞭,反正我愛了哈哈哈哈。框架
現已加入肯德基(KtArmor-MVP)豪華午飯,歡迎各位客官品嚐~dom
仍是那句話,KtArmor-MVP 封裝了基本 MVP結構的框架,是一款小而美的框架,麻雀雖小五章俱全。封裝了基礎的功能,小的項目,或者測試項目能夠直接拿來用,省時省力。但願你們喜歡~
最後,如有不妥,望小夥伴們指出。
感謝閱讀,下次再見