Leave a rating/review
Notes: 04. Create a HTTP Connection
The student materials have been reviewed and are updated as of July 2022.
After creating the NetworkStatusChecker, you can implement and API call or request, to register a user!
You’ll also need to format the data into JSON, to make it understandable to the server, and you’ll need to open an HTTP connection to the server, to send that data.
Finally, you need to worry about threading, and on which thread the request is executed, and on which you want to update the UI. A lot of steps there, but you’ll see that it’s rather easy to implement. Let’s get on it! :]
To start off with creating an API call, you need to use the network checker, to launch API calls only if there’s an internet connection.
Also, to enter the app, you first have to register a user, so head over to the RegisterActivity.kt.
Now add the following property to the class:
private val networkStatusChecker by lazy {
NetworkStatusChecker(getSystemService(ConnectivityManager::class.java))
}
This will fetch the Connectivity manager, used for checking the network info, from the system. After that, wrap the remoteApi call in the internet connection check:
networkStatusChecker.performIfConnectedToInternet {
...
}
That’s it from the Activity side, now move to the RemoteApi.kt. Change the registerUser code to the following:
Thread(Runnable {
}).start()
In order to follow that “avoid blocking the main thread” rule, you need to start a new Thread, and move the API call to that thread. By putting code in the Runnable above, you achieve just that. Once you’ve set up the thread, you need to add the code, to achieve an HttpURLConnection:
Thread(Runnable {
val connection = URL("$BASE_URL/api/register").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
}).start()
You first need to open a connection to a specific URL, and then set the request method, in this case a POST, as you’re sending data to the server, and the request properties, to use the JSON format for data.
If you check the URL, it’s using the base URL of the server, and then /api/register. The extra text after the base url is called the endpoint path.
An endpoint is a unique combination of a REST method, and a URL path, which holds unique functionality.
In this case, the combination of a POST method and /api/register creates the functionality to register a new user. Now add the following code:
Thread(Runnable {
val connection = URL("$BASE_URL/api/register").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
}).start()
This code describes the timeouts, in case the server doesn’t connect properly, and the doOutput & doInput properties let the connection perform data input or output. Now add the following code, to format the registration data into JSON:
Thread(Runnable {
val connection = URL("$BASE_URL/api/register").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"name\":\"${userDataRequest.name}\", \"email\":\"${userDataRequest.email}\", " +
"\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
}).start()
JSON has a specific format, as you’ve already learned, and because of that, you need to format it as in this snippet.
You start and finish with the braces, and then add each parameter name under String literals, then separate the key from the value with a colon, finally adding the value. All keys are separated with commas.
After formatting the JSON, you create a ByteArray out of it, because you’ll have to send the JSON as a series of bytes.The next step you need to do is open an output stream, to send the data, and write the bytes to the API endpoint:
Thread(Runnable {
val connection = URL("$BASE_URL/api/register").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"name\":\"${userDataRequest.name}\", \"email\":\"${userDataRequest.email}\", " +
"\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
try {
connection.outputStream.use { outputStream ->
outputStream.write(bytes)
}
} catch (error: Throwable) {
}
}).start()
You open a try/catch block first, to make sure things don’t crash when writing or receiving data.
Within it, you get the connection’s outputStream and write the bytes. You’re using use here, to automatically close the stream once the writing operation is done. After sending the data, you need to read the response:
Thread(Runnable {
val connection = URL("$BASE_URL/api/register").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"name\":\"${userDataRequest.name}\", \"email\":\"${userDataRequest.email}\", " +
"\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
try {
connection.outputStream.use { outputStream ->
outputStream.write(bytes)
}
val reader = InputStreamReader(connection.inputStream)
reader.use { input ->
val response = StringBuilder()
val bufferedReader = BufferedReader(input)
bufferedReader.useLines { lines ->
lines.forEach {
response.append(it.trim())
}
}
}
} catch (error: Throwable) {
}
}).start()
A lot’s happening here. You’re first creating an InputStreamReader. to read the response from the connection’s input stream. Then once again, with use, you’re safely consuming the reader, and storing all the lines from the reader, to a StringBuilder.
You’re using a BufferedReader here, because it’s sometimes better to read the input one chunk at a time, to avoid overwhelming the program. Finally, after reading the input, or in case of an error within catch, you have to send back the data through a callback:
Thread(Runnable {
val connection = URL("$BASE_URL/api/register").openConnection() as HttpURLConnection
connection.requestMethod = "POST"
connection.setRequestProperty("Content-Type", "application/json")
connection.setRequestProperty("Accept", "application/json")
connection.readTimeout = 10000
connection.connectTimeout = 10000
connection.doOutput = true
connection.doInput = true
val body = "{\"name\":\"${userDataRequest.name}\", \"email\":\"${userDataRequest.email}\", " +
"\"password\":\"${userDataRequest.password}\"}"
val bytes = body.toByteArray()
try {
connection.outputStream.use { outputStream ->
outputStream.write(bytes)
}
val reader = InputStreamReader(connection.inputStream)
reader.use { input ->
val response = StringBuilder()
val bufferedReader = BufferedReader(input)
bufferedReader.useLines { lines ->
lines.forEach {
response.append(it.trim())
}
}
onUserCreated(response.toString(), null)
}
} catch (error: Throwable) {
onUserCreated(null, error)
}
connection.disconnect()
}).start()
The callbacks take in either the response value, or an error. And then in the checks, you check if one of the pieces of information is null, and depening on which of the pieces is null, you either show an error, or a success message.
Before running the app, there is one more small piece of code you need to add to the RegisterActivity. Open the file, and add the code as follows:
runOnUiThread {
...
}
Because the register request is on the background thread, you cannot update the UI from it. You need to switch to the main thread, and you do that using runOnUiThread(). Now run the project, and register a user.
And it works! But the success Toast message is a bit weird! It’s also in the JSON format. :[ You’ll see how to extract the message in the next episode!