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
.
registerUser
:
val jsonUser = JSONObject().apply {
put("username", username)
put("email", email)
put("password", password)
}
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.
Below the previous code, add the following:
val connection =
URL("https://http-api-93211a10efe2.herokuapp.com/user/register").openConnection() as HttpsURLConnection
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
}
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.
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)
}
use
is a handy extension function that automatically closes Closeable
instances.
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)
}
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
.
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()
}
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.
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.