Making GET Requests and Handling Responses

Welcome to the second lesson in our journey of interacting with APIs in Kotlin. Previously, we laid a strong foundation by understanding RESTful APIs and using manual tools to craft and send a GET request to an endpoint. Now, we are transitioning to automate this process using libraries, specifically OkHttp. While Kotlin's standard library provides basic HTTP functionalities, OkHttp excels with its user-friendly API and robust capabilities for handling HTTP requests more efficiently and smoothly. This makes it an invaluable tool in web development and API integration, providing features like connection pooling, transparent GZIP, and response caching, which go beyond the basic functionalities offered by the native options.

Setting Up the Environment

To make HTTP requests in Kotlin, we will use the OkHttp library, a user-friendly and powerful library for handling HTTP requests. If you are working within an IDE like IntelliJ IDEA, you can integrate OkHttp by adding the following line to your build.gradle file in the dependencies section if you're using Gradle:

dependencies {
    implementation("com.squareup.okhttp3:okhttp:4.9.3")
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
}

After adding the dependencies, make sure to sync your project to include the necessary libraries.

Setting Up Serialization

Setting up JSON serialization is crucial for parsing responses because it allows us to convert JSON data from the API into Kotlin objects that we can easily manipulate in our code. This offers a seamless integration between the data formats used by web services and the Kotlin programming language, significantly simplifying the process of handling JSON data.

To do this you need to add the kotlinx serialization library:

dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.3.2")
}

And don't forget to apply the plugin:

plugins {
    kotlin("plugin.serialization") version "1.5.30"
}
Defining the Base URL

When interacting with an API, it's helpful to define a base URL for the service you're communicating with. This makes your code more modular and easy to maintain, allowing you to change the root of your API URL in one place without modifying each request.

// Base URL for the API
val baseUrl = "http://localhost:8000"

By setting this base URL, we can easily concatenate endpoints for different services within the API, making our code cleaner and more adaptable.

APIs often run on specific ports to separate different services or applications on the same server. In local development, localhost typically refers to 127.0.0.1, the local loopback address, but the API server must still be listening on the specified port. The default HTTP port is 80, and HTTPS is 443, but many development APIs use custom ports (e.g., 8000 in this case). Ensure that the API server is running on the correct port before making requests, as an incorrect port leads to connection failures.

Performing a Basic GET Request

Let's dive into the process of fetching data from an API using Kotlin's OkHttp library. Our goal is to retrieve a list of to-do items from the /todos endpoint using the GET method.

import okhttp3.OkHttpClient
import okhttp3.Request
import kotlinx.serialization.*
import kotlinx.serialization.json.*

// Define a data class to represent a to-do item; each property corresponds to a JSON field
@Serializable
data class Todo(
    val description: String,
    val done: Boolean,
    val id: Int,
    val title: String
)

fun fetchTodos() {
    // Initialize the OkHttpClient which will be used to send requests
    val client = OkHttpClient()

    // Build a GET request to the /todos endpoint, appended to the base URL
    val request = Request.Builder()
        .url("$baseUrl/todos")
        .build()

    // Execute the request and handle the response
    client.newCall(request).execute().use { response ->
        // Print the raw response body received from the server
        println("Raw Response:")
        println(response.body?.string())
    }
}

fetchTodos()

By using OkHttp's newCall().execute() method, we send a GET request to the constructed full URL. The response from the server is then printed.

Handling Successful Requests (Status Code: 200)
Handling Bad Requests (Status Code: 400)

Errors can occur on both the client and server sides. A 400 status code indicates a mistake in the request, often due to incorrect syntax from the client side. To understand these errors better, you can print the response body, which provides more details about what went wrong.

// Check if the server response status code is 400 (Bad Request)
if (response.code == 400) {
    println("\nBad Request. The server could not understand the request due to invalid syntax.")

    // Retrieve the response body which contains error details
    val error = response.body?.string()

    // Print the error details for further analysis
    println("Error Details: $error")
}
Handling Unauthorized Requests (Status Code: 401)

A 401 status code indicates an unauthorized request, often due to missing or invalid credentials. This situation requires the user to address authentication issues to proceed.

// Check if the server response status code is 401 (Unauthorized)
if (response.code == 401) {
    println("\nUnauthorized. Access is denied due to invalid credentials.")

    // Retrieve the response body which contains error details
    val error = response.body?.string()

    // Print the error details to understand the authentication issue
    println("Error Details: $error")
}
Handling Not Found Errors (Status Code: 404)

When encountering a 404 status code, it means the requested resource is not found, often pointing to a missing resource or incorrect endpoint.

// Check if the server response status code is 404 (Not Found)
if (response.code == 404) {
    println("\nNot Found. The requested resource could not be found on the server.")

    // Retrieve the response body which contains error details
    val error = response.body?.string()

    // Print the error details to determine what might be missing or incorrect
    println("Error Details: $error")
}
Handling Internal Server Errors (Status Code: 500)

A 500 status code reflects an internal server error, indicating the server encountered an unexpected situation. Such cases usually require investigation on the server side to resolve the issue.

// Check if the server response status code is 500 (Internal Server Error)
if (response.code == 500) {
    println("\nInternal Server Error. The server has encountered a situation it doesn't know how to handle.")

    // Retrieve the response body which contains error details
    val error = response.body?.string()

    // Print the error details to understand the server-side issue
    println("Error Details: $error")
}
Handling Unexpected Status Codes

For responses falling outside the common codes, a generic approach captures these cases, ensuring all responses are analyzed for potential issues.

// Handle any unexpected status codes that do not fall into predefined categories
else {
    println("\nUnexpected Status Code: ${response.code}")

    // Retrieve the response body which may contain additional information
    val error = response.body?.string()

    // Print the error details to help diagnose unexpected responses
    println("Error Details: $error")
}

By handling these diverse status codes, we ensure robust API interactions and a better understanding of the server's communication.

Conclusion, Key Takeaways, and Next Steps

In this lesson, we've explored the powerful capability of Kotlin's OkHttp library to make GET requests to an API. We've seen how to retrieve and handle responses effectively, interpreting HTTP status codes to understand the server's communication. This knowledge is crucial for creating reliable interactions with APIs. As you move on to the practice exercises, focus on experimenting with the code snippets and handling various status codes to solidify your understanding. In future lessons, we will build on this foundation, unlocking the potential to perform more complex tasks like updating and manipulating API data. Keep up the great work as you advance through the course!

Sign up
Join the 1M+ learners on CodeSignal
Be a part of our community of 1M+ users who develop and demonstrate their skills on CodeSignal