Complete Guide to Making API Requests in Kotlin: From Basics to Practice

Dec 08, 2025 · Programming · 9 views · 7.8

Keywords: Kotlin | API Requests | OkHttp | Android Development | Network Programming

Abstract: This article provides a comprehensive guide to implementing API requests in Kotlin, with a focus on using the OkHttp library. Starting from project configuration, it systematically covers permission settings, client initialization, request building, and asynchronous processing through practical code examples. The guide also discusses best practices for network requests and common problem-solving approaches, offering valuable technical insights for Android developers.

Project Configuration and Dependency Management

Before writing API request code, it's essential to configure necessary dependencies and permissions in the project. For Android projects using the OkHttp library, dependencies must be declared in the build.gradle file. For example, adding OkHttp version 3.8.1 can be done as follows:

dependencies {
    // Other dependencies
    implementation 'com.squareup.okhttp3:okhttp:3.8.1'
}

Additionally, since network requests require internet access, the network permission must be declared in the AndroidManifest.xml file:

<uses-permission android:name="android.permission.INTERNET" />

OkHttp Client Initialization

The core of the OkHttp library is the OkHttpClient class, which manages HTTP connection pools, caches, and interceptors. In Activities or Fragments, the client instance is typically declared as a member variable to ensure its lifecycle aligns with the component. Here's a typical initialization example:

class MainActivity : AppCompatActivity() {
    private val client = OkHttpClient()
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        
        // Call API request method
        executeRequest("https://api.github.com/users/Evin1-/repos")
    }
}

Request Building and Execution

Building HTTP requests requires the Request.Builder class, which provides a fluent API for setting URL, request method, headers, and other parameters. For simple GET requests, construction can be done as follows:

fun executeRequest(url: String) {
    val request = Request.Builder()
        .url(url)
        .build()
    
    // Execute request asynchronously
    client.newCall(request).enqueue(object : Callback {
        override fun onFailure(call: Call, e: IOException) {
            // Handle request failure
            Log.e("NetworkError", "Request failed: ${e.message}")
        }
        
        override fun onResponse(call: Call, response: Response) {
            // Handle successful response
            val responseBody = response.body()?.string()
            Log.d("NetworkResponse", "Response content: $responseBody")
        }
    })
}

The enqueue method is used for asynchronous requests, which is recommended in Android development as it doesn't block the main thread. The callback interface Callback provides two methods: onFailure for handling network errors or timeouts, and onResponse for processing server responses.

Response Handling and Error Management

Proper API response handling requires consideration of various scenarios. The response object Response contains status codes, headers, and response body data. In practical applications, status codes should be checked and different HTTP statuses handled appropriately:

override fun onResponse(call: Call, response: Response) {
    when (response.code()) {
        in 200..299 -> {
            // Successful response
            val responseData = response.body()?.string()
            // Parse and process data
        }
        in 400..499 -> {
            // Client error
            Log.w("ClientError", "Client error: ${response.code()}")
        }
        in 500..599 -> {
            // Server error
            Log.w("ServerError", "Server error: ${response.code()}")
        }
        else -> {
            // Other status codes
            Log.w("UnknownStatus", "Unknown status code: ${response.code()}")
        }
    }
    
    // Important: Close response body to prevent memory leaks
    response.close()
}

Note that the response body ResponseBody is a resource that needs to be closed manually. While the string() method automatically closes the stream, when handling large responses or using other reading methods, the close() method should be called explicitly.

Advanced Configuration and Best Practices

For production applications, more detailed configuration of OkHttpClient is recommended. For example, connection timeouts, read timeouts, and write timeouts can be set, and interceptors can be added for logging or authentication:

val client = OkHttpClient.Builder()
    .connectTimeout(10, TimeUnit.SECONDS)
    .readTimeout(30, TimeUnit.SECONDS)
    .writeTimeout(30, TimeUnit.SECONDS)
    .addInterceptor(HttpLoggingInterceptor().apply {
        level = HttpLoggingInterceptor.Level.BODY
    })
    .build()

In Android development, lifecycle management is also crucial. Network requests should be canceled when components are destroyed to prevent memory leaks and invalid callbacks:

class MainActivity : AppCompatActivity() {
    private lateinit var client: OkHttpClient
    private var currentCall: Call? = null
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        client = OkHttpClient()
    }
    
    fun executeRequest(url: String) {
        val request = Request.Builder().url(url).build()
        currentCall = client.newCall(request)
        currentCall?.enqueue(object : Callback {
            // Callback implementation
        })
    }
    
    override fun onDestroy() {
        super.onDestroy()
        currentCall?.cancel()  // Cancel ongoing requests
    }
}

The article also discusses the fundamental difference between HTML tags like <br> and characters like \n, where the former creates line breaks in HTML rendering while the latter represents newline characters in text processing. In code examples, special characters must be properly escaped, such as the angle brackets in print("<T>") to avoid parsing errors.

Alternative Approaches and Further Reading

Beyond OkHttp, Kotlin developers can consider other network request libraries. Retrofit is a type-safe HTTP client particularly suitable for REST API calls. Combined with Kotlin coroutines, it enables more concise asynchronous code:

// Retrofit interface definition
interface GitHubService {
    @GET("users/{user}/repos")
    suspend fun listRepos(@Path("user") user: String): List<Repo>
}

// Usage example
val retrofit = Retrofit.Builder()
    .baseUrl("https://api.github.com/")
    .addConverterFactory(GsonConverterFactory.create())
    .build()

val service = retrofit.create(GitHubService::class.java)

// Call within a coroutine
lifecycleScope.launch {
    try {
        val repos = service.listRepos("Evin1-")
        // Process data
    } catch (e: IOException) {
        // Handle exception
    }
}

For complex application architectures, consider combining Dagger for dependency injection, using RxJava or Flow for data streams, and adopting MVP or MVVM design patterns to separate concerns. These advanced topics are beyond this article's scope, but developers can explore them further through relevant documentation and sample projects.

Copyright Notice: All rights in this article are reserved by the operators of DevGex. Reasonable sharing and citation are welcome; any reproduction, excerpting, or re-publication without prior permission is prohibited.