Automating API Requests with Kotlin: An Alternative Approach

Welcome to the second lesson in our series on interacting with APIs in Kotlin. Up until now, we've looked at RESTful APIs and learned how to manually send a GET request. Today, we'll automate this process further using Kotlin, exploring native methods for making HTTP requests. This approach streamlines the process and removes external dependencies, promoting a more integrated handling of HTTP interactions in Kotlin applications.

Performing a Basic GET Request

Let's start by fetching data from an API using Kotlin's standard library for HTTP operations. Our goal is to retrieve a list of to-do items from the /todos endpoint using the GET method.

Kotlin
1import java.net.HttpURLConnection 2import java.net.URL 3import kotlinx.serialization.* 4import kotlinx.serialization.json.* 5 6// Define a data class to represent a to-do item; each property corresponds to a JSON field 7@Serializable 8data class Todo( 9 val description: String, 10 val done: Boolean, 11 val id: Int, 12 val title: String 13) 14 15fun fetchTodos() { 16 // Define the URL for the GET request 17 val url = URL("http://localhost:8000/todos") 18 19 // Open connection 20 with(url.openConnection() as HttpURLConnection) { 21 requestMethod = "GET" // Optional, default is GET 22 23 println("Raw Response:") 24 25 // Read and print the response 26 inputStream.bufferedReader().use { 27 val response = it.readText() 28 println(response) 29 } 30 } 31} 32 33fun main() { 34 fetchTodos() 35}

The code for performing a basic GET request in Kotlin demonstrates how to fetch data from a specified URL using Kotlin's standard library for HTTP operations. The process involves several steps:

  1. Import Necessary Libraries: The code imports classes from the java.net package for URL connections and kotlinx.serialization for handling JSON serialization and deserialization.

  2. Define a Data Class: The Todo data class is defined with properties: description, done, id, and title. These correspond to the fields expected in the JSON response from the API.

  3. fun fetchTodos(): This function is responsible for making the GET request.

    • Define the URL: The URL object is created with the endpoint http://localhost:8000/todos.

    • Open Connection: The openConnection() method is called on the URL object, returning an instance of HttpURLConnection.

    • Set Request Method: The request method is explicitly set to "GET", though it is optional as GET is the default method.

  4. Read and Print the Response: The response from the server is read using an input stream that is wrapped in a bufferedReader(). The use function ensures that the reader is closed after the operation. The response text is printed to the console.

  5. Main Function: The main function calls fetchTodos() to execute the GET request.

This setup provides a simple, native method for performing HTTP GET requests in Kotlin without external dependencies.

Handling Successful Requests (Status Code: 200)

We need to process the response of a successful GET request. By inspecting the HTTP status code, our client will act accordingly and deserialize the JSON response.

Kotlin
1with(url.openConnection() as HttpURLConnection) { 2 requestMethod = "GET" 3 4 if (responseCode == 200) { // Success 5 println("Todos retrieved successfully:") 6 7 inputStream.bufferedReader().use { 8 val json = it.readText() 9 10 // Deserialize the JSON string into a list of Todo objects 11 val todos = Json.decodeFromString<List<Todo>>(json) 12 13 // Iterate through the list and print each Todo 14 todos.forEach { todo -> 15 println("Title: ${todo.title}, Description: ${todo.description}, Done: ${todo.done}") 16 } 17 } 18 } 19}

Note that a status code of 200 indicates success, but it doesn't always ensure the response contains the expected data. Some APIs may return an empty array ([]) when no records are found. It's important to implement conditional checks to handle such scenarios.

Handling Bad Requests (Status Code: 400)

When dealing with HTTP requests, a status code of 400 indicates that the request was malformed. We'll handle it by printing detailed error information.

Kotlin
1if (responseCode == 400) { // Client-side error 2 println("\nBad Request. The server could not understand the request due to invalid syntax.") 3 4 errorStream?.bufferedReader().use { 5 val error = it?.readText() 6 println("Error Details: $error") 7 } 8}
Handling Unauthorized Requests (Status Code: 401)

A status code of 401 signifies an authorization failure. It often requires verifying or providing credentials.

Kotlin
1if (responseCode == 401) { // Unauthorized 2 println("\nUnauthorized. Access is denied due to invalid credentials.") 3 4 errorStream?.bufferedReader().use { 5 val error = it?.readText() 6 println("Error Details: $error") 7 } 8}
Handling Not Found Errors (Status Code: 404)

A 404 error indicates the requested resource was not found, usually suggesting a nonexistent endpoint.

Kotlin
1if (responseCode == 404) { // Resource not found 2 println("\nNot Found. The requested resource could not be found on the server.") 3 4 errorStream?.bufferedReader().use { 5 val error = it?.readText() 6 println("Error Details: $error") 7 } 8}
Handling Internal Server Errors (Status Code: 500)

An Internal Server Error (500) typically indicates a problem on the server's end that requires diagnosis.

Kotlin
1if (responseCode == 500) { // Server-side error 2 println("\nInternal Server Error. The server has encountered a situation it doesn't know how to handle.") 3 4 errorStream?.bufferedReader().use { 5 val error = it?.readText() 6 println("Error Details: $error") 7 } 8}
Handling Unexpected Status Codes

In cases where the response code is not anticipated, a general handler is essential to ensure robust result management.

Kotlin
1else { 2 println("\nUnexpected Status Code: $responseCode") 3 4 errorStream?.bufferedReader().use { 5 val error = it?.readText() 6 println("Error Details: $error") 7 } 8}

Managing varied responses through status codes involves ensuring comprehensive error handling for effective API communication.

Conclusion, Key Takeaways, and Next Steps

In this lesson, we've explored using Kotlin's built-in capabilities to perform API requests without additional libraries. This method utilizes HttpURLConnection for direct HTTP communication in Kotlin, fostering resource-efficient applications. Understanding response codes and appropriate handling ensures effective interaction with online resources as you create resilient applications. In upcoming lessons, we'll delve into more sophisticated tasks, like data manipulation and state changes through APIs. Continue progressing in your learning journey as we expand on these basics.

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