Function Signature
Now that you have the network status check in place, move on to implementing an API call for user registration. Open MovieDiaryApi.kt. registerUser
has already been prepared for you.
registerUser
is a suspend function that takes parameters for username
, email
, password
, and a callback function: onUserRegistered
. It’s designed to perform network operations without blocking the main thread. It does that by switching to the IO dispatcher. Android requires you to move networking code away from the main thread. That’s because you want to leave the main thread free to render UI. If you try to make a network request on the main thread, you’ll get a NetworkOnMainThreadException
.
Creating JSON Payload
To send user data to the API, you must format it as JSON. Add the following code to registerUser
:
val jsonUser = JSONObject().apply {
put("username", username)
put("email", email)
put("password", password)
}
To construct the request payload, you use JSONObject
. It’s a built-in class that helps with JSON manipulation. You create a new instance of JSONObject
and insert user data by passing key-value pairs into put
. With the payload ready, you’ll proceed with opening a connection to the server.
Setting up HTTPS Connection
Below the previous code, add the following:
val connection =
URL("https://http-api-93211a10efe2.herokuapp.com/user/register").openConnection() as HttpsURLConnection
This creates a URL object pointing to the API’s registration endpoint. You also cast it to HttpsURLConnection
, a built-in class for handling HTTPS connections.
Now that you have a handle on the connection, you’ll add some properties to it. Add the following:
connection.apply {
setRequestProperty("Content-Type", "application/json")
setRequestProperty("Accept", "application/json")
requestMethod = "POST"
readTimeout = 10000
connectTimeout = 10000
doOutput = true
doInput = true
}
The Content-Type
header identifies the format of the data in the request. The Accept
header tells the server what type of content is acceptable as a response. Because you want to create a new user on the server, you’ll use the POST method.
You set the timeouts to 10 seconds, in case the server can’t connect or respond in time. doOutput
and doInput
properties let the connection perform data input and output.
Writing the Request Body
The next step is to send the user data to the server. Add the following code to do that:
val body = jsonUser.toString().toByteArray()
try {
connection.outputStream.use { stream ->
stream.write(body)
}
First, you convert the JSON object into a byte array, which is required to write data to the output stream. Then, you open the output stream of the connection and write the byte array to it. You wrap everything with a try-catch block, just in case something goes wrong. use
is a handy extension function that automatically closes Closeable
instances.
Reading the Response
At this point, data has been sent to the server and you’re expecting a response. To read the response, write the following code:
val reader = InputStreamReader(connection.inputStream)
reader.use {
val response = StringBuilder()
val bufferedReader = BufferedReader(reader)
bufferedReader.useLines { lines ->
lines.forEach {
response.append(it.trim())
}
}
onUserRegistered(response.toString(), null)
}
You create an instance of InputStreamReader
and pass it a connection.inputStream
. You’ll use it to read the response.
BufferedReader
and StringBuilder
are responsible for efficient response reading and string concatenation.
bufferedReader.useLines
reads the response line by line, trimming each line and appending it to the StringBuilder
, and, finally, closes the reader instance. If this point in code has been reached, you can assume there was no error and you can invoke the callback, passing in the response as a string and setting the error to null
.
Error Handling
The final thing to do is to handle possible errors. Do this by finishing the try-catch block started before. Add the following code:
} catch (error: Throwable) {
onUserRegistered(null, Throwable("An error occurred. Please try again."))
} finally {
connection.disconnect()
}
This block catches any exceptions that might occur while executing the API call. If an error occurs, the callback is invoked, passing null
as the response and a Throwable
containing an error message. The finally
block ensures the connection is closed, regardless of whether an error occurred or not.
Running the App
Run the app, turn off airplane mode, and register a new user by clicking the Register button, filling in the fields, and clicking Register again. Bear in mind, that the API server might need a few seconds to wake up if it hasn’t been used lately.
If the server responds successfully, you return to the Login screen. Otherwise, you see an error message.