Working with Networking
1. Adding Internet Permission
| Permission | Description | Usage |
|---|---|---|
| android.permission.INTERNET | Allows applications to open network sockets. | Required for all network operations including HTTP requests, WebSocket connections, etc. |
| android.permission.ACCESS_NETWORK_STATE | Allows applications to access information about networks. | Useful for checking network connectivity status before making requests. |
Example: Declaring Permissions in AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<application ... >
...
</application>
</manifest>
2. Making HTTP Requests
Example: Basic GET Request with OkHttp
import okhttp3.*
val client = OkHttpClient()
fun makeGetRequest() {
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
client.newCall(request).execute().use { response ->
if (response.isSuccessful) {
println(response.body?.string())
} else {
println("Request failed: ${response.code}")
}
}
}
| Method | Description | Notes |
|---|---|---|
| OkHttpClient() | Creates an instance of the HTTP client | Can be customized with interceptors, timeouts, etc. |
| Request.Builder() | Builds a new HTTP request | Chainable methods for URL, headers, body |
| .url() | Sets the URL for the request | Must be a valid HTTP/HTTPS URL |
| .build() | Finalizes the request object | Must be called before executing |
| client.newCall(request).execute() | Executes the HTTP request synchronously | Returns a Response object |
| response.isSuccessful | Checks if the HTTP status code is 2xx | Useful for handling success vs error cases |
| response.body?.string() | Gets the response body as a string | Must be consumed once, and is nullable |
Note: For asynchronous requests, use
enqueue() instead of execute().Warning: Never perform network operations on the main thread in Android. Always use background threads or coroutines.
3. Performing GET Requests
Example: Basic GET Request with OkHttp
import okhttp3.OkHttpClient
import okhttp3.Request
val client = OkHttpClient()
val request = Request.Builder()
.url("https://api.example.com/users")
.build()
val response = client.newCall(request).execute()
println(response.body?.string())
| Property | Description | Values |
|---|---|---|
| url | Endpoint to fetch data from | "https://api.example.com/users" |
| method | HTTP method to use | "GET" |
| headers | Optional headers to send | mapOf("Authorization" to "Bearer token") |
Note: Ensure network permissions are added to
AndroidManifest.xml for HTTP requests.4. Performing POST Requests
| Property | Description | Example |
|---|---|---|
| url | Endpoint URL to send the POST request | "https://api.example.com/users" |
| body | Request body as a JSON string or object | "{\n "name": "John",\n "email": "john@example.com"\n}" |
| headers | Map of HTTP headers to include in the request | mapOf("Content-Type" to "application/json") |
Example: Sending a POST Request with OkHttp
import okhttp3.*
val client = OkHttpClient()
val json = "{\n \"name\": \"John\",\n \"email\": \"john@example.com\"\n}"
val body = RequestBody.create(json, MediaType.get("application/json"))
val request = Request.Builder()
.url("https://api.example.com/users")
.post(body)
.addHeader("Content-Type", "application/json")
.build()
try {
val response = client.newCall(request).execute()
println(response.body?.string())
} catch (e: IOException) {
e.printStackTrace()
}
Example: Sending a POST Request with Retrofit
import retrofit2.Retrofit
import retrofit2.converter.gson.GsonConverterFactory
import retrofit2.http.Body
import retrofit2.http.POST
interface ApiService {
@POST("users")
suspend fun createUser(@Body user: User): Response<User>
}
data class User(
val name: String,
val email: String
)
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)
val user = User("John", "john@example.com")
5. Handling Network Responses
| Property | Description | Example |
|---|---|---|
| response.code | HTTP status code returned by the server | 200, 404 |
| response.body | Response body as a string or parsed object | "{"message": "Success"}" |
| response.headers | Map of response headers | {"Content-Type": "application/json"} |
Example: Handling Response Codes
val response = apiClient.fetchData()
when (response.code) {
200 -> {
val data = response.body
// Process successful response
}
404 -> {
// Handle not found error
}
else -> {
// Handle other errors
}
}
Example: Parsing JSON Response
val response = apiClient.fetchData()
if (response.code == 200) {
val json = response.body
val parsed = Gson().fromJson(json, MyDataClass::class.java)
}
Note: Always check the response code before parsing the body to avoid runtime errors.
Warning: Never assume a successful response code. Always validate and handle potential errors.
6. Parsing JSON Responses
Example: Parsing JSON with Gson
import com.google.gson.Gson
import com.google.gson.annotations.SerializedName
data class User(
@SerializedName("id") val id: Int,
@SerializedName("name") val name: String,
@SerializedName("email") val email: String
)
val json = "{"id": 1, "name": "John Doe", "email": "john@example.com"}"
val gson = Gson()
val user = gson.fromJson(json, User::class.java)
| Method | Description | Notes |
|---|---|---|
| fromJson() | Parses JSON string into object | Requires type adapter or annotations |
| toJson() | Serializes object to JSON | Opposite of fromJson() |
Note: Gson requires
@SerializedName for field mapping when JSON keys differ from Kotlin property names.Example: Using Moshi for JSON Parsing
import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
data class Product(
val id: Int,
val name: String,
val price: Double
)
val moshi = Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
val jsonAdapter = moshi.adapter(Product::class.java)
val productJson = "{"id": 101, "name": "Laptop", "price": 999.99}"
val product = jsonAdapter.fromJson(productJson)
JSON Parsing Libraries Comparison
- Gson: Easy to use, built-in with Android Studio
- Moshi: Faster, more efficient, modern approach
- Json-Kotlin: Kotlin-first, type-safe, less boilerplate
7. Checking Network Connectivity
Example: Check Network Status
import android.content.Context
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkInfo
import android.os.Build
fun isNetworkAvailable(context: Context): Boolean {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)
capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true ||
capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true ||
capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET) == true
} else {
val networkInfo: NetworkInfo? = connectivityManager.activeNetworkInfo
networkInfo?.isConnectedOrConnecting == true
}
}
| Method | Description | Return Type | Notes |
|---|---|---|---|
| isNetworkAvailable() | Checks if the device has active network connection | Boolean |
Supports both legacy and modern Android versions |
Note: For Android 6.0 (API level 23) and above, use
ConnectivityManager.getNetworkCapabilities() for better network information.Warning: Network availability does not guarantee internet access. Additional checks may be required for full connectivity validation.
Network Connectivity Overview
- ConnectivityManager is the primary class for checking network status
- Use
getNetworkCapabilities()for Android 6.0+ (API level 23) - Use
activeNetworkInfofor older versions - Check for specific network transports like
TRANSPORT_WIFI,TRANSPORT_CELLULAR
8. Monitoring Network Changes
Example: Registering for Network Status Updates
import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkInfo
import android.os.Build
class NetworkChangeReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
val connectivityManager = context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
val network = connectivityManager.activeNetwork
val capabilities = connectivityManager.getNetworkCapabilities(network)
val isConnected = capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) == true ||
capabilities?.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) == true
if (isConnected) {
// Handle network connected
} else {
// Handle network disconnected
}
} else {
val networkInfo = connectivityManager.activeNetworkInfo
val isConnected = networkInfo?.isConnected == true
if (isConnected) {
// Handle network connected
} else {
// Handle network disconnected
}
}
}
}
// Register the receiver in your Activity or Service
val filter = IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION)
val receiver = NetworkChangeReceiver()
registerReceiver(receiver, filter)
| Property | Description | Values |
|---|---|---|
| ConnectivityManager.CONNECTIVITY_ACTION | Intent action broadcast when network connectivity changes | android.net.conn.CONNECTIVITY_CHANGE |
| NetworkCapabilities.TRANSPORT_WIFI | Indicates network is using WiFi transport | true or false |
| NetworkCapabilities.TRANSPORT_CELLULAR | Indicates network is using cellular transport | true or false |
Note: For Android 6.0 (API level 23) and above, use
ConnectivityManager.registerDefaultNetworkCallback instead of BroadcastReceiver.
Example: Using NetworkCallback (API 23+)
import android.net.ConnectivityManager
import android.net.Network
import android.net.NetworkCapabilities
import android.net.NetworkRequest
import android.os.Build
class NetworkCallback : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
super.onAvailable(network)
// Handle network available
}
override fun onLost(network: Network) {
super.onLost(network)
// Handle network lost
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
val hasInternet = networkCapabilities.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET)
if (hasInternet) {
// Handle internet available
} else {
// Handle no internet
}
}
}
// Register the callback in your Activity or Service
val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
val networkRequest = NetworkRequest.Builder()
.addTransportType(NetworkCapabilities.TRANSPORT_WIFI)
.addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
.build()
connectivityManager.registerNetworkCallback(networkRequest, NetworkCallback())
Network Change Monitoring Summary
- BroadcastReceiver: Legacy approach for Android versions below 6.0
- NetworkCallback: Modern approach for Android 6.0+
- Permissions: Requires
ACCESS_NETWORK_STATEin manifest - Network Types: Supports WiFi, cellular, and other transport types
9. Handling Network Exceptions
| Exception Type | Description | Handling Strategy |
|---|---|---|
| IOException | Thrown when network operations fail due to connectivity issues or timeouts. | Implement retry logic with exponential backoff and notify user of connection problems. |
| SocketTimeoutException | Occurs when a socket read or write times out. | Use timeout configurations and display a "Request timed out" message to the user. |
| UnknownHostException | Occurs when the host address cannot be resolved (e.g., DNS failure). | Inform user that the server could not be reached and suggest checking network settings. |
| HttpException | Indicates an HTTP error response (e.g., 404, 500). | Parse the status code and provide user-friendly error messages based on the code. |
Example: Handling Network Exceptions in Retrofit
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com")
.addConverterFactory(GsonConverterFactory.create())
.build()
val apiService = retrofit.create(ApiService::class.java)
try {
val response = apiService.fetchData()
if (response.isSuccessful) {
// Process successful response
} else {
// Handle HTTP error
when (response.code()) {
404 -> println("Resource not found")
500 -> println("Server error occurred")
}
}
} catch (e: IOException) {
// Handle network errors
println("Network error occurred: ${e.message}")
} catch (e: HttpException) {
// Handle HTTP errors
println("HTTP error occurred: ${e.code()}")
}
Note: Always handle
IOException and HttpException separately to provide appropriate feedback to the user based on the type of error.10. Using Network Security Configuration
Example: Basic Network Security Config
<?xml version="1.0" encoding="utf-8"?>
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="@raw/trusted_ca" />
</trust-anchors>
</base-config>
</network-security-config>
| Element | Description | Notes |
|---|---|---|
| network-security-config | Root element for network security configuration | Must be declared in res/xml/ |
| base-config | Applies to all network traffic | Can set cleartextTrafficPermitted |
| trust-anchors | Defines trusted CA certificates | Use src to reference raw resource |
Note: Network security config is available from Android 7.0 (API level 24) onwards.
Example: Allowing Cleartext Traffic
<network-security-config>
<base-config cleartextTrafficPermitted="true">
<trust-anchors>
<certificates src="@raw/trusted_ca" />
</trust-anchors>
</base-config>
</network-security-config>
Example: Using Custom Certificate
<network-security-config>
<base-config cleartextTrafficPermitted="false">
<trust-anchors>
<certificates src="@raw/custom_ca" />
</trust-anchors>
</base-config>
</network-security-config>
11. Implementing Certificate Pinning
Certificate pinning is a security technique that ensures your app only accepts connections from specific certificates or public keys, mitigating man-in-the-middle (MITM) attacks.
| Property | Description | Values |
|---|---|---|
| Certificate Pinning | Specifies the certificate or public key to trust for a domain | sha256/... or sha1/... |
Example: Using OkHttp with Certificate Pinning
import okhttp3.CertificatePinner
import okhttp3.OkHttpClient
import okhttp3.Request
val certificatePinner = CertificatePinner.Builder()
.add("api.example.com", "sha256/AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA=")
.add("api.example.com", "sha256/BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB=")
.build()
val client = OkHttpClient.Builder()
.certificatePinner(certificatePinner)
.build()
val request = Request.Builder()
.url("https://api.example.com/data")
.build()
val response = client.newCall(request).execute()
Note: Pinning should be used carefully as it can break connectivity if the server's certificate changes.
Warning: If the pinned certificate does not match, the connection will be rejected.