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