Network Requests with Retrofit in Android

Jun 5 2024 · Kotlin 1.9.22, Android 14, Android Studio Hedgehog | 2023.1.1

Lesson 01: Understand Network

Demo 2

Episode complete

Play next episode

Next
Transcript

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.

See forum comments
Cinema mode Download course materials from Github
Previous: Connecting to the internet Next: Conclusion