Kotlin的魔能機甲——KtArmor網絡調用封裝(四)

前言

繼上次分享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

Presenter

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())
            })
        }
        
        ... 省略部分代碼
    }
}
複製代碼

Model

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 (showExceptionapi

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 !!架構

階段三 (DSL)

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結構的框架,是一款小而美的框架,麻雀雖小五章俱全。封裝了基礎的功能,小的項目,或者測試項目能夠直接拿來用,省時省力。但願你們喜歡~

最後,如有不妥,望小夥伴們指出。

Login例子源碼

BasePresenter源碼

Kotlin的魔能機甲——KtArmor(一)

Kotlin的魔能機甲——KtArmor插件篇(二)

Kotlin的魔能機甲——KtArmor(三)

KtArmor-MVP 源碼傳送門

感謝閱讀,下次再見

相關文章
相關標籤/搜索