Android | Send JSON Content to the Cloud Using HttpUrlConnection & Kotlin Coroutine
Sending data to the cloud is a common task in mobile applications. Here we will consume an open REST service to practice sending data in JSON format to a cloud server. We will use HttpURLConnection to make an HTTP POST request to the server. Then, we can verify that our request is successful by going to hmkcode-api.appspot.com/links.html page.
Objectives
- How to send HTTP POST request to the cloud with JSON content.
- How to check network connection?
- How to use kotlin coroutine to perform network operations on a separate thread?
Environment, Tools & Library
used in this post
- Android Studio 3.5.1
HttpURLConnection
client.- REST service
http://hmkcode-api.appspot.com/rest/link/add
to post data. - https://hmkcode-api.appspot.com/links.html to view our stored data.
About the app
We are going to build a sample app that stores links info in the cloud. The app takes three inputs from the user link title, url and optional tags. When the user click send, the app will build a JSON object holding the three inputs, add the built JSON object to the POST request body and finally execute the POST request.
( 1 ) Create new Android Project
Create new android application keep default options.
- Application Name: post-json
- Package Name: com.hmkcode
- Language: Kotlin
- Minimum SDK: API 19: Android 4.4 (KitKat)
( 2 ) Add Permissions
- Add the following two lines to the
AndroidMainifest.xml
file
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
( 3 ) Design App Layout
- Add the following views to
activity_main.xml
. TextView
for displaying network connection info.- Three
EditText
for title, URL & tags account.
activity_main.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout ...>
<TextView
android:id="@+id/tvIsConnected"
... />
<com.google.android.material.textfield.TextInputLayout
...>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etTitle"
...
android:hint="Title..."/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
...>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etUrl"
...
android:hint="example.com"/>
</com.google.android.material.textfield.TextInputLayout>
<com.google.android.material.textfield.TextInputLayout
...>
<com.google.android.material.textfield.TextInputEditText
android:id="@+id/etTags"
...
android:hint="tag1;tag2;..."/>
</com.google.android.material.textfield.TextInputLayout>
<Button
android:id="@+id/btnSend"
android:text="Send"
...
android:onClick="send"/>
<TextView
android:id="@+id/tvResult"
.../>
</LinearLayout>
Dependencies
- You need to include the following dependencies to build.gradle
build.gradle (Module: post-json)
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'com.google.android.material:material:1.1.0-beta01'
implementation 'androidx.core:core-ktx:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
// Coroutine
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.2"
implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.3.2"
implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.2.0-alpha05"
}
( 4 ) Send JSON to Server
MainActivity.kt
is the only class where we need to code the following:
- a. Check network connectivity.
- b. Build
httpPost()
function with IO dispatcher. - c. Build
send()
function to launchhttpPost()
inlifecycleScope
coroutine scope.
a. Check network connectivity
- Before making network operation check notwork connectivity status.
- To check network connectivity use getActiveNetworkInfo() & isConnected().
private fun checkNetworkConnection(): Boolean {
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connMgr.activeNetworkInfo
val isConnected: Boolean = if(networkInfo != null) networkInfo.isConnected() else false
if (networkInfo != null && isConnected) {
// show "Connected" & type of network "WIFI or MOBILE"
tvIsConnected.text = "Connected " + networkInfo.typeName
// change background color to red
tvIsConnected.setBackgroundColor(-0x8333da)
} else {
// show "Not Connected"
tvIsConnected.text = "Not Connected"
// change background color to green
tvIsConnected.setBackgroundColor(-0x10000)
}
return isConnected
}
b. Build httpPost()
function with IO dispatcher.
- Create httpPost(String url) method
- httpPost() method does the following:
- Use
withContext(Dispatcher.IO){}
to run code on IO thread. - Creates
HttpURLConnection
- Builds
JSONObject
- Adds JSON content to POST request body
- executes the POST request
- returns response message
- Use
Throws(IOException::class, JSONException::class)
private suspend fun httpPost(myUrl: String): String {
val result = withContext(Dispatchers.IO) {
val url = URL(myUrl)
// 1. create HttpURLConnection
val conn = url.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8")
// 2. build JSON object
val jsonObject = buidJsonObject()
// 3. add JSON content to POST request body
setPostRequestContent(conn, jsonObject)
// 4. make POST request to the given URL
conn.connect()
// 5. return response message
conn.responseMessage + ""
}
return result
}
}
c. Build send()
function to launch httpPost()
in lifecycleScope
coroutine scope.
- Launch coroutine using activity
lifecycleScope
. - Show the result on the
tvResult
public fun send(view:View) {
Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show()
// clear text result
tvResult.setText("")
if (checkNetworkConnection())
lifecycleScope.launch {
val result = httpPost("https://hmkcode-api.appspot.com/rest/link/add")
tvResult.setText(result)
}
else
Toast.makeText(this, "Not Connected!", Toast.LENGTH_SHORT).show()
}
MainActivity.java complete code
package com.hmkcode
import android.content.Context
import android.net.ConnectivityManager
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import android.view.View
import android.widget.EditText
import android.widget.TextView
import android.widget.Toast
import androidx.lifecycle.lifecycleScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import org.json.JSONException
import org.json.JSONObject
import java.io.BufferedWriter
import java.io.IOException
import java.io.OutputStreamWriter
import java.net.HttpURLConnection
import java.net.URL
import javax.net.ssl.HttpsURLConnection
class MainActivity : AppCompatActivity() {
lateinit var tvIsConnected: TextView
lateinit var etTitle: EditText
lateinit var etUrl: EditText
lateinit var etTags: EditText
lateinit var tvResult: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
tvIsConnected = findViewById<TextView>(R.id.tvIsConnected)
etTitle = findViewById<EditText>(R.id.etTitle)
etUrl = findViewById<EditText>(R.id.etUrl)
etTags = findViewById<EditText>(R.id.etTags)
tvResult = findViewById<TextView>(R.id.tvResult)
checkNetworkConnection()
}
public fun send(view:View) {
Toast.makeText(this, "Clicked", Toast.LENGTH_SHORT).show()
// clear text result
tvResult.setText("")
if (checkNetworkConnection())
lifecycleScope.launch {
val result = httpPost("https://hmkcode-api.appspot.com/rest/link/add")
tvResult.setText(result)
}
else
Toast.makeText(this, "Not Connected!", Toast.LENGTH_SHORT).show()
}
@Throws(IOException::class, JSONException::class)
private suspend fun httpPost(myUrl: String): String {
val result = withContext(Dispatchers.IO) {
val url = URL(myUrl)
// 1. create HttpURLConnection
val conn = url.openConnection() as HttpsURLConnection
conn.requestMethod = "POST"
conn.setRequestProperty("Content-Type", "application/json; charset=utf-8")
// 2. build JSON object
val jsonObject = buidJsonObject()
// 3. add JSON content to POST request body
setPostRequestContent(conn, jsonObject)
// 4. make POST request to the given URL
conn.connect()
// 5. return response message
conn.responseMessage + ""
}
return result
}
private fun checkNetworkConnection(): Boolean {
val connMgr = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkInfo = connMgr.activeNetworkInfo
val isConnected: Boolean = if(networkInfo != null) networkInfo.isConnected() else false
if (networkInfo != null && isConnected) {
// show "Connected" & type of network "WIFI or MOBILE"
tvIsConnected.text = "Connected " + networkInfo.typeName
// change background color to red
tvIsConnected.setBackgroundColor(-0x8333da)
} else {
// show "Not Connected"
tvIsConnected.text = "Not Connected"
// change background color to green
tvIsConnected.setBackgroundColor(-0x10000)
}
return isConnected
}
@Throws(JSONException::class)
private fun buidJsonObject(): JSONObject {
val jsonObject = JSONObject()
jsonObject.accumulate("title", etTitle.getText().toString())
jsonObject.accumulate("url", etUrl.getText().toString())
jsonObject.accumulate("tags", etTags.getText().toString())
return jsonObject
}
@Throws(IOException::class)
private fun setPostRequestContent(conn: HttpURLConnection, jsonObject: JSONObject) {
val os = conn.outputStream
val writer = BufferedWriter(OutputStreamWriter(os, "UTF-8"))
writer.write(jsonObject.toString())
Log.i(MainActivity::class.java.toString(), jsonObject.toString())
writer.flush()
writer.close()
os.close()
}
}
(5) Run & Check
- Run the app
- Enter title, URL & tags.
- Click Send
- Visit http://hmkcode-api.appspot.com/link.html to check