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

00:02Now 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.

00:20registerUser 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.

00:52To 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)
}

01:13To 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.

01:31Below the previous code, add the following:

val connection =
  URL("https://http-api-93211a10efe2.herokuapp.com/user/register").openConnection() as HttpsURLConnection

01:46This 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.

01:58Now 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
}

02:18The 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.

02:45The 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)
  }

02:59First, 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.

03:21At 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)
}

03:39You 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.

04:13The 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()
}

04:35This 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.

04:54Run 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.

05:11If 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