preface

When we make a network request, it is almost always in the form of callback:

request.execute(callback)
Copy the code
callback = {
    onSuccess =  { res ->
        // TODO
    }

    onFail =  { error -> 
        // TODO
    }
}
Copy the code

I’ve been used to writing this way for a long time. Even when there are difficulties and doubts, it is still not clear what the alternative is. Maybe some friends will say RxJava. Yes, RxJava can alleviate some troubles caused by callback to some extent, but is subscriber really separated from callback in essence?

request.subscribe(subscriber)
...
subscriber = ...
Copy the code
request.subscribe({
    // TODO Success
}, {
    // TODO Error
})
Copy the code

Kotlin offers a cleaner asynchronous approach. The code is not split into two pieces or even N pieces, the logic is still sequential.

doAsync {
    val response = request.execute()
    uiThread {
        // TODO
    }
}
Copy the code

Of course, this is not the point I want to say this time, after all, this is only the preface

first

Some days ago, I learned Kotlin’s coroutine. Frankly speaking, although I understood the concept and theory of coroutine to a certain extent, I felt dizzy (actually lazy) when I saw so many complicated apis.

What is a coroutine, I suggest you Google.

One day, my friend told me that Anko supports coroutines. I was excited and immediately went to Github to have a look. As for why I’m excited, anyone who knows Anko will understand. When I actually opened the Anko-Coroutines wiki, I was shocked to find that the wiki only had two functions for what I considered to be such a complex coroutine.

See here estimated many partners to be impatient, ok, let’s enter the code time:

fun getData(): Data { ... }
fun showData(data: Data) { ... }

async(UI) {
    val data: Deferred<Data> = bg {
	    // Runs in background
	    getData()
    }

    // This code is executed on the UI thread
    showData(data.await())
}
Copy the code

Let’s ignore the outermost async(UI) for the moment:

val data: Deferred<Data> = bg {
	// Runs in background    
    getData()
}

// This code is executed on the UI thread
showData(data.await())
Copy the code

{bg {}} {getData();} {bg {}} {{}}; Does that make sense?

The bg {} wrapped code will eventually return a Deferred object, and the await function of the Deferred object plays a key role here — blocking the current coroutine and waiting for the result.

While async(UI) {}, which has been ignored for the time being, starts an asynchronous coroutine task on the UI thread. Because it’s asynchronous, it won’t block the entire UI thread if it blocks; Since it’s still on the UI thread, we can safely do UI operations. Accordingly, bg {} can be understood as async(BACKGROUND) {}, so you can do network requests on Android.

So, the code above is really a little story between the UI coroutine on the UI thread, and the BG coroutine on the BG thread.

contrast

Compared to the previous doAsync — uiThread code, it looks similar, but only just. DoAsync creates a new thread in which the code you write can no longer be synchronized with a thread outside doAsync. In order to create an association, you need to use the previous callback method.

As we have seen in the code above, with coroutines, we can make the coroutine wait for another coroutine, even if the other coroutine belongs to another thread.

The ability to write asynchronous tasks in the same way you write synchronous code is probably one of the reasons many people like coroutines. I tried it here, using coroutines with Retrofit for network requests:

asyncUI {
    val deferred = bg{// in the BG threadbgCall interface server.getapistore ().login("173176360"."123456"Textview.execute ()} // simulate a pop-up loading progress bar or something like that"loading"Val Response = deferred.await() val response = deferred.await(if (response.isSuccessful) {
        textView.text = response.body().toString()
    } else {
        toast(response.errorBody().string())
    }
}
Copy the code

In case you’re impatient, everything I want to say is in the notes.

The body of the

Melon eaters: What? Is this the beginning of the text? Below: of course, with respect to the content of that point above, do I bashfully mean to say to play flower?

All right, joking aside, I have to say that if it was just that piece of code up there, it would be worth something, but not much. The advantages over traditional callback have not yet been demonstrated. So how do you show that advantage? Look at the code:

Async (UI) {// Suppose these are two different API requests val Deferred1 =bg {
        Server.getApiStore().login("173176360"."123456").execute()
    }

    val deferred2 = bg {
        Server.getApiStore().login("173176360"."123456").execute()} val res1 = deferred1.await() val res2 = deferred2.await( res1.body().toString() + res2.body().toString() }Copy the code

See? I haven’t done any encapsulation yet, and even RxJava can’t write logic like this easily. This is the beauty of writing asynchronous tasks in synchronous code.

Remember how we used to write logic like this? What if we get a few more of these? Does callback Hell already exist?

With a little wrapping, we can see a request like this:

asyncUI {
    val deferred = bg {
        Server.getApiStore().login("173176360"."123456").execute()
    }

    textView.text = "loading"Val info = deferred. Wait (toast) // or Log // Textview.text = info.toString()}Copy the code

Add a default way to handle exceptions while waiting, instead of breaking the flowing logic and writing if-else code every time.

What if I want to do something else besides toast and log?

asyncUI {
    val deferred = bg {
        Server.getApiStore().login("173176360"."123456").execute()
    }

    textView.text = "loading"Val info = deferred.handleException {// Custom exception handling, Textview.text = info.toString()} TextView.text = info.toString()}Copy the code

And then someone said, you’re making it really hard for me, if I succeed and I fail and I do the same thing, wouldn’t I have to write two copies of the same code?

asyncUI {
    val deferred = bg {
        Server.getApiStore().login("173176360"."123456").execute()
    }

    textView.text = "loading"Deferred. Wait (THROUGH) //typeTextview.text = textView.text ="done"
}
Copy the code

What difference does it make if I just want to reuse part of the code? You u should use the primitive await function. Of course, I still encapsulate here, at least can convert Response into Data, more or less save the dessert

asyncUI {
    val deferred = bg {
        Server.getApiStore().login("1731763609"."123456").execute()
    }

    textView.text = "loading"Val info = deferred. Wait (THROUGH) val info = deferred. Wait (THROUGH) val info = deferredtypeTextview.text = textView.text ="done"
    
    if(info.issuccess) {// TODO succeeded}else{// TODO failed}}Copy the code

Combine the situation with the multiple API requests above

AsyncUI {// Suppose these are two different API requests val Deferred1 =bg {
        Server.getApiStore().login("173176360"."123456").execute()
    }

    val deferred2 = bg {
        Server.getApiStore().login("173176360"."123456"Textview.execute ()} // While the background is calling the API, I can still do whatever I want in the UI coroutine"loading"Delay (5, timeunit.seconds) // Wait for the UI coroutine to complete, Val Response = deferred1.wait(TOAST) val Response = deferred1.wait(TOAST) Textview.text = response.toString()} textView.text = response.toString()}Copy the code

Well, that’s all for this introduction. If you think you haven’t spent enough, you can have a try