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 源码传送门

感谢阅读,下次再见

相关文章
相关标签/搜索