This article describes how to encapsulate a retorfit-based, coroutine network request, which is not a standard DSL. For details on how to change the style to a DSL network request, see this article: Kotlin network request like Gradle

preface

Coroutines have been available for some time now, and they take up fewer resources than threads and make it easier to switch between threads. Starting with RetroFIT 2.6.0, RetroFIT directly supports the use of oai and coroutines. So I’ll show you how to quickly encapsulate a dSL-based request method based on coroutines. This article takes the most common MVP architecture as an example.

Encapsulated request mode

You can see from this basic request:

  • Start is the main thread, we can do some UI things like popovers and so on; Of course, if you don’t need to go directly to the second request operation, you can
  • Request is a network request operation. The thread is a child thread
  • Then is the result of the network request. There are onError and onSucces and onComplete methods that operate on the main thread
  • The last curly bracket is the onCompete method, whether you have it or not, and here’s kotlin’s lambda form

Compared to the RXJava approach, this approach is much simpler. At its simplest, a single request and then operators can make a network request. At the same time, this method can prevent memory leaks and automatically cancel requests when the activity has finished, whereas RXJava requires more complex processing to prevent memory leaks.

retrofit

RetrofitHelper

Without further explanation for Retrofit, we create a RetrofitHelper class for network requests. I’m just going to create a class that’s a RetorFit object for convenience, and I’m not going to cache anything. Developers can make some encapsulation as they see fit. The main difference is that coroutine based RetroFIT network requests do not need to add an Adapter when creating retroFIT, as rXJava does.

class RetrofitHelper { companion object{ fun getApi(): ApiService{val client = okHttpClient.builder ().connectTimeout(10, timeunit.seconds) Timeununit.seconds) // Read timeout. WriteTimeout (10, writeTimeout) Timeunit.seconds) // Write timeout.build () val retrofit = retrofit.builder ().client(client).baseurl ("https://api.github.com/") .addConverterFactory(GsonConverterFactory.create()).build() return retrofit.create(ApiService::class.java) } } }Copy the code

ApiService

The main point to note is that the interface method of the network request needs to be decorated by suspend, because the method modification needs to take place inside the coroutine or an error will be reported at request time.

interface ApiService {

    @GET("users/JavaNoober/repos")
    suspend fun getUserInfo(): List<UserBean>
}
Copy the code

CoroutineDSL

Because lifecycle will need to be automatically cancelled at the end of the page, lifecycle will be packaged with Google’s lifecycle library, because in the higher versions of the Support library lifecycle will be integrated by default so it will be easier to use and in a few lines of code you can do what you need.

start

/**
 * execute in main thread
 * @param start doSomeThing first
 */
infix fun LifecycleOwner.start(start: (() -> Unit)): LifecycleOwner{
    GlobalScope.launch(Main) {
        start()
    }
    return this
}
Copy the code

Add the start method in LifecycleOwner to turn on the coroutine and perform some UI operations in the Main coroutine.

request

/** * execute in io thread pool * @param loader http request */ infix fun <T> LifecycleOwner.request(loader: suspend () -> T): Deferred<T> { return request(loader) } /** * execute in io thread pool * @param loader http request * @param needAutoCancel need to cancel when activity destroy */ fun <T> LifecycleOwner.request(loader: suspend () -> T, needAutoCancel: Boolean = true): Deferred<T> { val deferred = GlobalScope.async(Dispatchers.IO, start = CoroutineStart.LAZY) { loader() } if(needAutoCancel){ lifecycle.addObserver(CoroutineLifecycleListener(deferred,  lifecycle)) } return deferred } internal class CoroutineLifecycleListener(private val deferred: Deferred<*>, private val lifecycle: Lifecycle): LifecycleObserver { @OnLifecycleEvent(Lifecycle.Event.ON_DESTROY) fun cancelCoroutine() { if (deferred.isActive) { deferred.cancel() } lifecycle.removeObserver(this) } }Copy the code

The ***request*** method is a network request method, we can use a Boolean value to control whether to automatically cancel. If necessary, we add a LifecycleObserver to the current LifecycleOwner and automatically cancel the request when onDestroy occurs. Loader a network request method that takes retrofit and operates on the IO thread provided by the coroutine, returning a Deferred object that can be used to control automatic cancellation.

then

/** * execute in main thread * @param onSuccess callback for onSuccess * @param onError callback for onError * @param onComplete callback for onComplete */ fun <T> Deferred<T>.then(onSuccess: suspend (T) -> Unit, onError: suspend (String) -> Unit, onComplete: (() -> Unit)? = null): Job { return GlobalScope.launch(context = Main) { try { val result = [email protected]() onSuccess(result) } catch (e: Exception) { e.printStackTrace() when (e) { is UnknownHostException -> onError("network is error!" ) is TimeoutException -> onError("network is error!" ) is SocketTimeoutException -> onError("network is error!" ) else -> onError("network is error!" ) } }finally { onComplete? .invoke() } } }Copy the code

The then method is a callback to onSuccess, onError, and onCompete. OnComplete is optional and null by default. Defereed is in the subcoroutine, so there will be no thread blocking. Defereed is in the subcoroutine, so there will be no thread blocking. Once the request is complete, we can do a series of processing on the callback result.

Presenter

Because this article is based on a common Lifecycle architecture we will put the network request method in Presenter and the network request method encapsulated above will be in Lifecycle and Presenter does not implement the Lifecycle method so we will need to do some encapsulation. I’m going to put it directly in MainPresenter, so I can actually write it in BasePresenter, so I don’t have to write it every time.

class MainPresenter : DefaultLifecycleObserver, LifecycleOwner {
    private val TAG = "MainPresenter"

    private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)

    override fun getLifecycle(): Lifecycle = lifecycleRegistry

    override fun onDestroy(owner: LifecycleOwner) {
        lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
        lifecycle.removeObserver(this)
    }
}

class MainActivity : AppCompatActivity() {

    private val mainPresenter: MainPresenter by lazy { MainPresenter() }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        lifecycle.addObserver(mainPresenter)
    }

    override fun onDestroy() {
        super.onDestroy()
        lifecycle.removeObserver(mainPresenter)
    }
}
Copy the code

We define Presenter as both LifecycleOwner and LifecycleObserver. LifecycleObserver is defined to allow a presenter to listen for the Activity’s lifecycle. LifecycleOwner is defined to make network requests in it, and we added to the override onDestroy:

       lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)
Copy the code

Such Activity time, onDestroy will go before the definition of CoroutineLifecycleListener cancelCoroutine method, to get help.

Complete call process

class MainActivity : AppCompatActivity() { private val mainPresenter: MainPresenter by lazy { MainPresenter() } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) lifecycle.addObserver(mainPresenter) mainPresenter.doHttpRequest() } override fun onDestroy() { super.onDestroy() lifecycle.removeObserver(mainPresenter) } }  class MainPresenter : DefaultLifecycleObserver, LifecycleOwner { private val TAG = "MainPresenter" private var lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this) override fun getLifecycle(): Lifecycle = lifecycleRegistry override fun onDestroy(owner: LifecycleOwner) { lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY) lifecycle.removeObserver(this) } / print the results are as follows: * * * * * MainPresenter: start doHttpRequest: currentThreadName: main * MainPresenter: request doHttpRequest:currentThreadName:DefaultDispatcher-worker-2 * MainPresenter: onSuccess doHttpRequest:currentThreadName:main * MainPresenter: UserBean(login=null, id=61097549, node_id=MDEwOlJlcG9zaXRvcnk2MTA5NzU0OQ==, avatar_url=null, gravatar_id=null, url=https://api.github.com/repos/JavaNoober/Album, html_url=https://github.com/JavaNoober/Album, followers_url=null, following_url=null, gists_url=null, starred_url=null, subscriptions_url=null, organizations_url=null, repos_url=null, events_url=https://api.github.com/repos/JavaNoober/Album/events, received_events_url=null, type=null, site_admin=false, name=Album, company=null, blog=null, location=null, email=null, hireable=null, bio=null, public_repos=0, public_gists=0, followers=0, following=0, created_at=2016-06-14T06:28:05Z, updated_at=2016-06-14T06:40:26Z) * MainPresenter: onComplete doHttpRequest:currentThreadName:main */ fun doHttpRequest() { start { Log.e(TAG, "start doHttpRequest:currentThreadName:${Thread.currentThread().name}") }.request { Log.e(TAG, "request doHttpRequest:currentThreadName:${Thread.currentThread().name}") RetrofitHelper.getApi().getUserInfo() }.then(onSuccess = { Log.e(TAG, "onSuccess doHttpRequest:currentThreadName:${Thread.currentThread().name}") Log.e(TAG, it[0].toString()) }, onError = { Log.e(TAG, "onError doHttpRequest:currentThreadName:${Thread.currentThread().name}") }) { Log.e(TAG, "onComplete doHttpRequest:currentThreadName:${Thread.currentThread().name}") } } }Copy the code

Once the above encapsulation is complete, the network request can be made. In the actual development process, it can be further encapsulated.

conclusion

All the core code adds up to only about 100 lines, but it does a safe, lightweight, and easy network request operation. Because Lifecycle has been used here, it is possible to consider using LiveData in the future, which will allow for processing of the data Lifecycle in a way that is difficult for other frameworks. The complete code has been uploaded to Github (CoroutinesHttp), and suggestions for better methods are welcome. For information on how to change the style to a DSL network request, see this article: Do network requests in Kotlin like using Gradle