继上次分享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结构的框架,是一款小而美的框架,麻雀虽小五章俱全。封装了基础的功能,小的项目,或者测试项目能够直接拿来用,省时省力。但愿你们喜欢~
最后,如有不妥,望小伙伴们指出。
感谢阅读,下次再见