Complete OkHttp Android Tutorial: Mastering Network Calls in Kotlin
Sending and receiving data from a backend server is the backbone of almost every modern Android application. While higher-level libraries like Retrofit often get the spotlight, understanding OkHttp—the powerful HTTP client that actually executes those requests under the hood—is a critical skill for any Android developer.
In this comprehensive OkHttp Android tutorial, you will learn the exact steps to implement OkHttp in Kotlin. We will cover everything from setting up dependencies and making asynchronous GET/POST requests, to sending JSON payloads and debugging with logging interceptors.
What is OkHttp and Why Use It?
Developed by Square, OkHttp is the industry-standard HTTP client for Android and Java. It is designed to be highly efficient by default, offering:
- Connection Pooling: Reuses HTTP/2 connections to reduce request latency.
- Transparent GZIP: Automatically compresses data to save bandwidth.
- Response Caching: Prevents repeating network requests for unchanged data.
- Robust Error Handling: Silently recovers from common connection problems.
Let’s dive into the implementation.
Step 1: Add OkHttp Dependencies
To use OkHttp, you must add the library to your project. In modern Android development, it is highly recommended to use the Bill of Materials (BOM) inside your Version Catalog to ensure all OkHttp modules stay synchronized on the same version.
In your libs.versions.toml:
[versions]
okhttp = "5.0.0-alpha.14" # Check for the latest stable release
[libraries]
okhttp-bom = { group = "com.squareup.okhttp3", name = "okhttp-bom", version.ref = "okhttp" }
okhttp = { group = "com.squareup.okhttp3", name = "okhttp" }
okhttp-logging = { group = "com.squareup.okhttp3", name = "logging-interceptor" }
In your app/build.gradle.kts:
dependencies {
implementation(platform(libs.okhttp.bom))
implementation(libs.okhttp)
implementation(libs.okhttp.logging)
}
Step 2: Configure Android Internet Permissions
By default, Android apps cannot access the internet. You must explicitly grant this permission inside your AndroidManifest file.
In AndroidManifest.xml:
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Required to make HTTP requests -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:usesCleartextTraffic="true" <!-- Allows non-HTTPS traffic for local testing -->
...>
...
</application>
</manifest>
Pro Tip: In a production app, you should set
usesCleartextTraffic="false"and enforce secureHTTPSconnections.
Step 3: Initialize a Singleton OkHttpClient
A common mistake beginners make is instantiating a new OkHttpClient for every network call. You should create a single, shared instance and reuse it throughout your app. This allows OkHttp to pool connections and save memory.
We can also configure custom timeouts to prevent our app from hanging endlessly on poor networks.
import okhttp3.OkHttpClient
import java.util.concurrent.TimeUnit
val client = OkHttpClient.Builder()
.connectTimeout(15, TimeUnit.SECONDS)
.readTimeout(15, TimeUnit.SECONDS)
.writeTimeout(15, TimeUnit.SECONDS)
.build()
Step 4: Making an Asynchronous GET Request
A GET request retrieves data from a REST API. Because network calls take time, Android strictly prohibits running them on the Main (UI) thread.
To avoid the dreaded NetworkOnMainThreadException, we use OkHttp’s .enqueue() method. This executes the request asynchronously on a background thread.
import okhttp3.*
import java.io.IOException
fun fetchData(url: String) {
val request = Request.Builder()
.url(url)
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
// Handle network errors here
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use { // .use automatically closes the response body to prevent memory leaks
if (!response.isSuccessful) throw IOException("Unexpected code $response")
val responseData = response.body?.string()
// Switch back to the Main Thread to update the UI
runOnUiThread {
println("Server Response: $responseData")
}
}
}
})
}
Step 5: Sending a POST Request with JSON
A POST request sends data to the server (e.g., submitting a login form). To send JSON, you must create a RequestBody and declare the Content-Type as application/json.
OkHttp provides convenient Kotlin extension functions like .toMediaType() and .toRequestBody() to make this clean and readable.
import okhttp3.MediaType.Companion.toMediaType
import okhttp3.RequestBody.Companion.toRequestBody
fun postData(url: String, jsonPayload: String) {
val mediaType = "application/json; charset=utf-8".toMediaType()
val body = jsonPayload.toRequestBody(mediaType)
val request = Request.Builder()
.url(url)
.post(body)
.addHeader("Authorization", "Bearer YOUR_TOKEN_HERE") // Example of adding a header
.build()
client.newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
e.printStackTrace()
}
override fun onResponse(call: Call, response: Response) {
response.use {
if (response.isSuccessful) {
println("Upload Success: ${response.body?.string()}")
} else {
println("Server Error: ${response.code}")
}
}
}
})
}
Step 6: Debugging with a Logging Interceptor
When developing network layers, you need to see exactly what your app is sending and receiving. Instead of manually logging every string, use the HttpLoggingInterceptor.
Update your client builder to include the interceptor:
import okhttp3.logging.HttpLoggingInterceptor
// 1. Create the interceptor
val logging = HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BODY // Logs headers, body, and status codes
}
// 2. Add it to your client
val debugClient = OkHttpClient.Builder()
.addInterceptor(logging)
.build()
Note: Make sure to disable Level.BODY in production releases to avoid leaking sensitive user data into device logs!
Best Practices
- Reuse the Client: Use a single
OkHttpClientinstance for the entire application lifecycle. - Async is Key: Always use
.enqueue()(or Kotlin Coroutines) to keep the UI thread smooth. - Check Success: Always validate
response.isSuccessful(status codes 200-299) before reading data. - Resource Safety: Always wrap your response in
response.use { ... }so the network socket closes properly. - Thread Safety: Never update Android Views from the OkHttp callback. Use
runOnUiThread, LiveData, or CoroutineDispatchers.Main.
F.A.Q
What is the difference between OkHttp and Retrofit?
OkHttp is a low-level HTTP client responsible for making the actual network connections, sending requests, and reading responses. Retrofit is a high-level REST client built by the same company on top of OkHttp. Retrofit maps JSON responses directly to Kotlin data classes using annotations, making API integration faster.
Can I use OkHttp with Kotlin Coroutines?
Yes! While OkHttp uses callbacks for asynchronous requests (enqueue), you can run synchronous requests (execute()) safely inside a Coroutine using withContext(Dispatchers.IO), or wrap the callback into a suspendCancellableCoroutine for clean, modern Android architecture.
How do I fix the NetworkOnMainThreadException?
This exception crashes your app because you attempted to make a synchronous network call (execute()) on the main UI thread. To fix it, use OkHttp’s asynchronous .enqueue() method instead, which automatically runs the request on a background thread.
Why is my OkHttp request timing out?
Timeouts occur when the server takes too long to respond or the user has a poor internet connection. By default, OkHttp has a 10-second timeout. You can extend this by configuring .connectTimeout(), .readTimeout(), and .writeTimeout() on your OkHttpClient.Builder.
📌 Full Course Playlist https://www.youtube.com/playlist?list=PLO1OrQEU0vHNmD9Xqzs-qXwzzwrDvdhVu
~ ~ THANK YOU FOR READING ~ ~