preface
After sharing the basic usage method of KtArmor last time, in the network request logic, in the call, the total feeling is not elegant and intuitive, the problem of nesting too deep, which makes the code look bloated and ugly. So in this article, I share my approach to encapsulating network request invocations. I hope you enjoy it
To prepare
In the demo example process, I just used to play with the Interface API provided by Android. In terms of the framework, I used Retrofit + OkHttp + Coroutine, and the example was Kotlin + MVP architecture. If there are students who do not understand, you can learn the relevant framework first, otherwise the viewing effect is not good.
Phase one
class LoginActivity : MvpActivity<LoginContract.Presenter>(), LoginContract.View { ... Omit some codeoverride fun initListener(a) {
super.initListener() mBtnLogin.setOnClickListener { presenter.login(mEtAccount.str(), mEtPassword.str()) } } ... Omit some code}Copy the code
This is part of the code. Here I use the login function as an example. Normally, LoginActivity calls the Presenter layer to initiate a login request.
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){... Omit some code// Start a UI CoroutinepresenterScope.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()) }) } ... Omit some code}}Copy the code
Model
object LoginModel : BaseModel() {
suspend fun login(account: String, password: String): BaseResponse<LoginRsp> {
return launchIO { ApiManager.apiService.loginAsync(account, password).await() }
}
}
Copy the code
In the Presenter layer, start a UI Coroutine to initiate a login request. In the Model layer, simply call apiService to initiate a network request, and then call the logic for the view according to the success of the response. This is relatively “intuitive” in the way it is called. There is a duplicate code loginFail which “generally” displays a toast to remind the user of relevant information. The code looks bloated, so the next step is to encapsulate these steps as Kt extension functions
Stage 2 (Extension)
fun launchUI(block: suspend CoroutineScope. () -> Unit, error: ((Throwable) -> Unit)? = null) { presenterScope.launch { tryCatch({ block() }, { error? .invoke(it) ? : showException(it.toString()) }) } }Copy the code
We’ll start by extracting a launchUI method into the Presenter base class to encapsulate the Coroutine startup mode and make it easier to manage Coroutine. And add a default error Block method that displays log (showException) by default if no error argument is passed.
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())
}
}
Copy the code
Then add a Response extension that handles the logic of the network request. Add two parameters: SUCCESS and error (optional, toast is displayed by default). Here is the code to replace the successful extension method.
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){... Omit some code// Start a UI CoroutinelaunchUI({ view? .showLoading() LoginModel.login(account, password).execute({ loginRsp -> loginRsp? .let { view? .loginSuc(it) } }, {// TODO loginFail})}, {// TODO loginError
})
// omit TODOlaunchUI({ view? .showLoading() LoginModel.login(account, password).execute({ loginRsp -> loginRsp? .let { view? .loginSuc(it) } }) }) ... Omit some code}}Copy the code
The above is abstracted as an extension function that simplifies Presenter processing logic. By default, TODO tokens can be omitted. The default is toast a message. This is relatively “clean”. However, if the logic is complex, there will be too deep nesting. I recently learned about Domain-specific Language (DSL) and introduced a DSL approach to optimize these processes. Perfect!!!!!
Phase 3 (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
}
}
Copy the code
DSLS seem abstract. In the Presenter base class, create an inner class Execute, Declare the corresponding methods (request, onSuccess, onFail, onException). Store the corresponding blocks first, and then process them in the request method. The logic is similar to that of the extension, so I won’t go into details here. Let’s see what happens when it’s packaged into a DSL.
The effect
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()) } } }Copy the code
The end result is not very “elegant”, reduce the level of nesting, from top to bottom, intuitive, anyway I love hahahaha.
The last
Now KFC (Ktarmor-MVP) luxury lunch, welcome guests to taste ~
Again, ktarmor-MVP encapsulates the framework of the basic MVP structure. It is a small but beautiful framework that has all the elements in it. Encapsulate basic functions, small projects, or test projects can be directly used, save time and effort. I hope you enjoy it
Finally, if there is something wrong, hope friends point out.
Login example
BasePresenter source
Kotlin’s Magical Mecha — KtArmor(PART 1)
Kotlin’s Magic Mecha — KtArmor Plugin (part 2)
Kotlin’s Magical Mecha — KtArmor(Part 3)
Ktarmor-mvp source code portal
Thanks for reading, see you next time