Android | Using Kotlin Coroutine to Perform HTTP Request using HttpUrlConnection
Long-running task such as CPU intensive work, writing a file to disk or network request should NOT run on the main thread. Android, for example, will throw a NetworkOnMainThreadException if you try to perform an HTTP reqeust on the main thread. To resolve this issue, we can use Kotlin coroutine to perform long-running task on a separate thread. This post shows how to perform a non-blocking HTTP reqest using HttpUrlConnection and Kotlin coroutine.
Objectives
- What is coroutine?
- Why using coroutine?
- How to use coroutine to perform network reqeust?
Environment & Tools
Tools used in this post
- Android Studio 3.5
- Google Pixel 3
- Kotlin 1.3.50
- http://hmkcode-api.appspot.com/rest/api/hello/[NAME] this URL will return simple string “Hello, [NAME]”
What is Coroutine?
Coroutine is a feature of Kotlin that enables you to write asynchronous sequential code to manage long-running task in background threads.
For example, coroutine will allow you to write sequential code to make an HTTP request.
Why Using Coroutine?
Long-running tasks such as CPU intensive work, network requests, and disk IO should not run on main thread. Running such tasks on main thread may block UI interactions. However, running network request on the main thread is prevented by Android SDK. Android will throw an exception if we try to make an HTTP request on the main thread.
How to use coroutine to perform network reqeust?
In a previous post Connecting to the Network Using HttpUrlConnection, I explained how to perform HTTP GET reqeust using
HttpUrlConnection
andAsyncTask
on an Android app. Here we will replaceAsyncTask
with coroutine to perform the same task.
HTTP GET Request
- The simplest example for network operation is HTTP GET request.
- We create a new URL object by passing url string to the constructor.
- The URL object is used to establish an HttpURLConnection.
- Then, we execute the operation and receive the response as InputStream.
- Finally, we use helper method to convert InputStream to string.
private fun httpGet(myURL: String?): String {
val inputStream:InputStream
val result:String
// create URL
val url:URL = URL(myURL)
// create HttpURLConnection
val conn:HttpURLConnection = url.openConnection() as HttpURLConnection
// make GET request to the given URL
conn.connect()
// receive response as inputStream
inputStream = conn.inputStream
// convert inputstream to string
if(inputStream != null)
result = convertInputStreamToString(inputStream)
else
result = "Did not work!"
return result
}
Perform Network Operations on a Separate Thread Using AsyncTask
- Network operation should always run on a seperate thread to avoid UI freeze.
- The AsyncTask class provides one of the simplest ways to create separate thread from the UI thread.
- Create an inner class extending AsyncTask.
- Override doInBackground() & onPostExecute.
inner class HTTPAsyncTask : AsyncTask<String, Void, String>() {
override fun doInBackground(vararg urls: String?): String {
return HttpGet(urls[0])
}
override fun onPostExecute(result: String?) {
tvResult.setText(result)
}
}
- Now, we can execute the HTTP request as following
HTTPAsyncTask().execute("http://hmkcode-api.appspot.com/rest/api/hello/Android")
Perform Network Operations on a Separate Thread Using Coroutine
Meet suspend
suspend
is a reserved keyword in Kotlin that makes a function available to coroutines. Function marked with suspend
suspends execution until the result is returned. While waiting for the result, it unblocks the thread that it’s running on so other functions or coroutines can run.
private suspend fun httpGet(myURL: String?): String? {...}
The suspend keyword doesn’t specify the thread code runs on. Suspend functions may run on a background thread or the main thread.
Dispatcher
- Dispatcher controls which thread runs a coroutine.
- There are three dispatcher
Default
,IO
&Main
We will run our suspend function on IO thread using withContext(Dispatchers.IO)
private suspend fun httpGet(myURL: String?): String? {
val result = withContext(Dispatchers.IO) {
// HTTP GET request code...
}
return result
}
Coroutine Scope
- Coroutines run inside a CoroutineScope
- A scope controls the lifetime of coroutines.
- We can use
GlobalScope
tolaunch
our coroutine.
GlobalScope is a good default for launching work in background, however it is not recommend using GlobalScope.
We will launch the coroutine using GlobalScope
GlobalScope.launch(Dispatchers.Main) {
val result = httpGet("http://hmkcode-api.appspot.com/rest/api/hello/Android")
tvResult.setText(result)
}
- We can replace
GlobalScope
withLifecycleScope
. LifecycleScope
is one of the three built-in coroutine scopes in the architecture components contained in the KTX extensions.
Add the dependency below to use this scope.
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha05"
Now, you can use ‘lifecycleScope’ to launch the coroutine
lifecycleScope.launch {
val result = httpGet("http://hmkcode-api.appspot.com/rest/api/hello/Android")
tvResult.setText(result)
}