UNIT 4: Networking and Web Services in Android
1. Fundamentals of Android Networking
Network Security Model
-
Cleartext Traffic: By default, Android 9+ blocks HTTP (cleartext). Enable via
android:usesCleartextTraffic="true"inAndroidManifest.xmlor use Network Security Configuration XML for granular control. -
Network Security Configuration XML: Define in
res/xml/network_security_config.xml:<network-security-config> <domain-config cleartextTrafficPermitted="true"> <domain includeSubdomains="true">example.com</domain> </domain-config> <pin-set expiration="2025-12-31"> <pin digest="SHA-256">base64-encoded-hash</pin> </pin-set> </network-security-config>Reference in manifest:
android:networkSecurityConfig="@xml/network_security_config". -
Certificate Pinning: Pin server certificates/public keys to prevent MITM attacks. Use OkHttp’s
CertificatePinner.
Permissions
-
INTERNET: Required for any network access. Declared inAndroidManifest.xml:<uses-permission android:name="android.permission.INTERNET" /> -
ACCESS_NETWORK_STATE: To check network connectivity status. -
Runtime Permissions: Not needed for these (normal protection level). Only dangerous permissions (like location) require runtime requests.
Connectivity Management
-
Use
ConnectivityManagerto check active network:val connectivityManager = getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager val network = connectivityManager.activeNetwork val capabilities = connectivityManager.getNetworkCapabilities(network) val isConnected = capabilities?.hasCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET) -
Monitoring Changes: Register
ConnectivityManager.NetworkCallbackor useLiveDatawithNetworkCallbackin aViewModel.
[!TIP] Exam Focus: Always check network state before making requests. Cleartext restrictions are a common exam question—know how to enable/disable them securely.
2. HTTP and REST Principles
HTTP Protocol Basics
| Method | Idempotent? | Safe? | Typical Use |
|---|---|---|---|
| GET | Yes | Yes | Retrieve data |
| POST | No | No | Create resource |
| PUT | Yes | No | Full update |
| PATCH | No | No | Partial update |
| DELETE | Yes | No | Remove resource |
-
Status Codes:
-
2xxSuccess:200 OK,201 Created -
4xxClient Error:400 Bad Request,401 Unauthorized,404 Not Found -
5xxServer Error:500 Internal Server Error
-
-
Headers:
-
Content-Type: Request body format (e.g.,application/json) -
Accept: Expected response format -
Authorization: Credentials (e.g.,Bearer <token>)
-
RESTful API Design
-
Constraints: Stateless, uniform interface, client-server, cacheable, layered system.
-
Resource Naming: Use nouns, plural form (
/users,/users/123/orders). -
Idempotency: Repeating same request yields same result (GET, PUT, DELETE).
Data Formats
-
JSON: Lightweight, key-value pairs. Example:
{ "id": 1, "name": "Alice", "tags": ["admin", "user"] } -
XML: Verbose, uses tags. Rare in modern REST APIs.
-
Content Negotiation: Client sets
Acceptheader; server responds accordingly.
[!TIP] Common Pitfall: Confusing PUT (full update) vs PATCH (partial). Idempotency is frequently tested.
3. Android Networking APIs (Legacy & Core)
HttpURLConnection
-
Manual Handling:
val url = URL("https://api.example.com/data") val connection = url.openConnection() as HttpURLConnection connection.requestMethod = "GET" connection.connectTimeout = 15000 connection.readTimeout = 15000 val response = connection.inputStream.bufferedReader().readText() -
Drawbacks: Verbose, manual stream/error handling, no built-in caching.
Volley
-
RequestQueue: Singleton pattern recommended.
val queue = Volley.newRequestQueue(context) -
Request Types:
StringRequest,JsonObjectRequest,ImageRequest. -
Listeners:
val request = JsonObjectRequest( Request.Method.GET, url, null, Response.Listener { response -> /* handle JSON */ }, Response.ErrorListener { error -> /* handle error */ } ) queue.add(request) -
Caching: Automatic disk/memory cache based on HTTP headers.
-
Prioritization & Cancellation:
request.setPriority(Request.Priority.HIGH),request.cancel().
Comparison
| Feature | HttpURLConnection |
Volley |
|---|---|---|
| Threading | Manual (use AsyncTask/coroutines) |
Automatic (uses thread pool) |
| Caching | Manual implementation | Built-in |
| Use Case | Simple requests, low-level control | Rapid development, caching needed |
| Performance | Lightweight, no overhead | Higher memory for cache |
[!TIP] Exam Tip: Volley is deprecated in favor of Retrofit + Coroutines, but legacy questions may ask about it. Know when to choose which.
4. Retrofit: Type-Safe HTTP Client
Setup and Configuration
-
Gradle Dependencies:
implementation 'com.squareup.retrofit2:retrofit:2.9.0' implementation 'com.squareup.retrofit2:converter-gson:2.9.0' -
Retrofit Instance:
val retrofit = Retrofit.Builder() .baseUrl("https://api.example.com/") .addConverterFactory(GsonConverterFactory.create()) .build()
API Interface Definition
-
Annotations:
-
@GET("users"),@POST("users") -
@Path("id")for path parameters (/users/{id}) -
@Query("page")for query parameters (?page=1) -
@Bodyfor request body (POST/PUT)
-
-
Return Types:
-
Call<T>: Asynchronous (enqueue) or synchronous (execute). -
suspend fun: With coroutines (preferred).
-
Making Requests
-
Asynchronous (Call):
apiService.getUsers().enqueue(object : Callback<List<User>> { override fun onResponse(call: Call<List<User>>, response: Response<List<User>>) { if (response.isSuccessful) { /* use response.body() */ } } override fun onFailure(call: Call<List<User>>, t: Throwable) { /* handle error */ } }) -
Synchronous:
val response = apiService.getUsers().execute()(must not run on main thread). -
Cancellation:
call.cancel().
Response and Error Handling
-
Response<T>:-
response.isSuccessful:200..299range. -
response.body(): Parsed data (null if error). -
response.errorBody(): Raw error response (e.g.,{ "error": "Invalid" }).
-
-
Global Error Handling: Use
Interceptoror wrapper sealed class:sealed class Result<out T> { data class Success<T>(val data: T) : Result<T>() data class Error(val code: Int, val message: String) : Result<Nothing>() }
Custom Converters
-
Implement
Converter.Factoryfor non-standard formats (e.g., XML, protobuf). -
Example for custom JSON:
class CustomConverterFactory : Converter.Factory() { override fun responseBodyConverter(type: Type, annotations: Array<Annotation>, retrofit: Retrofit): Converter<ResponseBody, *> { return Converter { value -> /* custom parsing */ } } }
[!TIP] Critical: Always check
response.isSuccessfulbefore accessingbody(). Useconverter-gsonfor JSON; Moshi is an alternative.
5. Coroutines for Asynchronous Networking
Coroutine Fundamentals
-
launch: Fire-and-forget, returnsJob. -
async: ReturnsDeferred<T>(awaitable result). -
Dispatchers:
-
Dispatchers.IO: Network/disk operations. -
Dispatchers.Main: UI updates.
-
-
Structured Concurrency: Coroutines scoped to lifecycle (
viewModelScope,lifecycleScope).
Retrofit with Coroutines
-
API Interface:
interface ApiService { @GET("users") suspend fun getUsers(): List<User> } -
Usage in ViewModel:
viewModelScope.launch { try { val users = apiService.getUsers() _usersLiveData.postValue(users) } catch (e: Exception) { /* handle */ } }
Error Handling
-
try-catch: Aroundsuspendcalls. -
CoroutineExceptionHandler: For uncaught exceptions inlaunch. -
Resultwrapper:val result = runCatching { apiService.getUsers() }.
Combining Multiple Calls
-
Parallel Requests:
val deferred1 = async { apiService.getUsers() } val deferred2 = async { apiService.getPosts() } val users = deferred1.await() val posts = deferred2.await() -
With Kotlin Flow: Use
zip/combinefor streams.
[!TIP] Exam Focus:
viewModelScopeauto-cancels onViewModelclearance. Never useGlobalScopein Android.
6. JSON Parsing and Data Modeling
Data Classes/POJOs
-
Kotlin:
data class User( @SerializedName("user_id") val id: Int, val name: String, val email: String? = null // nullable ) -
Java: Use
@SerializedName(Gson) or@Json(Moshi).
Serialization/Deserialization
-
Gson:
val gson = GsonBuilder().create() val user = gson.fromJson(jsonString, User::class.java)- Custom Type Adapter: For special formats (e.g., date patterns).
-
Moshi: More modern, supports Kotlin
nullsafety better.val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build() val adapter = moshi.adapter(User::class.java)
Advanced Parsing
-
Polymorphic Deserialization: Use
@JsonAdapterwith customJsonReaderlogic. -
Date/Time: Register
JavaTimeAdapterforLocalDate,Instant.
[!TIP] Common Pitfall: Mismatched JSON field names → use
@SerializedName. Null fields require nullable types or default values.
7. Authentication and Authorization
Token-Based Authentication
-
Bearer Token:
val request = Request.Builder() .url(url) .addHeader("Authorization", "Bearer $token") .build() -
Storage:
-
SharedPreferences: Insecure for tokens. -
EncryptedSharedPreferences(AndroidX Security) orDataStore(preferred). -
Android Keystore: For highly sensitive tokens.
-
OAuth 2.0 Flows
-
Authorization Code Flow with PKCE: Standard for mobile.
-
Generate
code_verifierandcode_challenge. -
Use
AppAuthlibrary: simplifies token exchange.
-
-
Implicit Flow: Deprecated (tokens in URL fragment).
Request Interceptors
-
Add Auth Header Dynamically:
val interceptor = Interceptor { chain -> val request = chain.request().newBuilder() .addHeader("Authorization", "Bearer ${tokenProvider.getToken()}") .build() chain.proceed(request) } -
Token Refresh: Interceptor checks
401, refreshes token, retries request.
[!TIP] Security: Never store tokens in plain
SharedPreferences. UseEncryptedSharedPreferencesor Keystore.
8. Caching and Offline Support
HTTP Caching with OkHttp
-
Setup:
val cacheSize = 10L * 1024 * 1024 // 10 MB val cache = Cache(context.cacheDir, cacheSize) val client = OkHttpClient.Builder().cache(cache).build() -
Cache Control: Respects
Cache-Control,Expiresheaders. -
Force Network/Cache:
val request = Request.Builder() .cacheControl(CacheControl.FORCE_NETWORK) // or FORCE_CACHE .build()
Offline-First Architecture
-
Repository Pattern:
class UserRepository( private val localDataSource: UserLocalDataSource, private val remoteDataSource: UserRemoteDataSource ) { suspend fun getUsers(): Result<List<User>> { return if (hasNetwork()) { remoteDataSource.getUsers().also { localDataSource.saveUsers(it) } } else { localDataSource.getUsers() } } } -
Room Database: For local caching.
-
Network Boundary: Define when to use network vs cache.
WorkManager for Background Sync
-
Periodic Work:
val syncRequest = PeriodicWorkRequestBuilder<SyncWorker>(1, TimeUnit.HOURS) .setConstraints(Constraints.Builder().setRequiredNetworkType(NetworkType.CONNECTED).build()) .setBackoffCriteria(BackoffPolicy.EXPONENTIAL, 10, TimeUnit.SECONDS) .build() WorkManager.getInstance(context).enqueue(syncRequest)
[!TIP] Exam Key: Offline-first = check network → use remote + cache, else use local cache only.
9. Advanced Topics and Best Practices
Image Loading
-
Glide (recommended): Handles caching, placeholders, GIFs.
Glide.with(context).load(url).placeholder(R.drawable.loading).into(imageView) -
Picasso: Simpler API, less configurable.
-
Coil: Kotlin-first, uses coroutines.
WebSockets
-
OkHttp WebSocket:
val request = Request.Builder().url("wss://example.com/socket").build() val webSocket = client.newWebSocket(request, object : WebSocketListener() { override fun onMessage(webSocket: WebSocket, text: String) { /* handle */ } }) -
Reconnection: Implement in
onClosedwith exponential backoff.
File Uploads/Downloads
-
Multipart Upload (Retrofit):
@Multipart @POST("upload") suspend fun uploadFile(@Part file: MultipartBody.Part): Response<UploadResponse> -
Progress Listener: Wrap
ResponseBodyto track bytes read. -
DownloadManager: System service for large files (handles retries, notifications).
Performance Optimization
-
Connection Pooling: OkHttp default (5 connections, keep-alive).
-
GZIP Compression: OkHttp auto-compresses requests with
Content-Encoding: gzip. -
Image Compression: Resize/downsample with Glide
.override(width, height). -
Memory Leaks: Cancel calls in
onCleared()(ViewModel) oronDestroy()(Activity).
10. Testing Network Operations
Unit Testing with Mocks
-
Mock API Interface (Mockito):
val mockApi = mock(ApiService::class.java) `when`(mockApi.getUsers()).thenReturn(DeferredValue(listOf(user))) -
MockWebServer (OkHttp): Fake server for responses.
val server = MockWebServer() server.enqueue(MockResponse().setBody("{\"id\":1}")) val baseUrl = server.url("/")
Integration Testing
-
Fake Data Sources: In-memory Room DB, fake API returning
Result.success. -
Test Coroutines: Use
TestCoroutineDispatcherorStandardTestDispatcher.
Instrumentation Tests
-
Run on device/emulator with
androidTestsource set. -
Use
Espressofor UI,WorkManagerTestInitHelperfor WorkManager.
[!TIP] MockWebServer is essential—queues responses, inspect requests.
11. Debugging and Monitoring
Logging and Inspection
-
OkHttp Logging Interceptor:
val logging = HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY } -
Stetho: Chrome DevTools integration for network inspection.
-
Chrome DevTools:
chrome://inspectfor WebView/network.
Android Studio Tools
-
Network Profiler: Real-time traffic, size, speed.
-
Inspector: View HTTP headers/bodies in Profiler.
Crash and Analytics
-
Firebase Crashlytics: Log non-fatal network errors.
-
Custom Analytics: Track API success/failure rates (e.g., with Firebase Analytics).
12. Security Considerations
Transport Security
-
Enforce HTTPS:
android:usesCleartextTraffic="false"in manifest. -
Certificate Pinning: In Network Security Config XML (see Section 1).
-
Self-Signed Certs: For dev, add to network security config
<domain-config>with<trust-anchors>.
Data Security
-
Avoid Logs: Never log tokens/credentials. Use
BuildConfig.DEBUGguards. -
Secure Storage:
EncryptedSharedPreferencesorAndroid Keystorefor tokens. -
MITM Prevention: Pinning + HTTPS.
Input Validation
-
Client-Side: Validate user input before sending (e.g., email format).
-
Server-Side: Never trust client—always validate on server.
13. Common Pitfalls and Troubleshooting
Threading Issues
-
Network on Main Thread: Crash with
NetworkOnMainThreadException. Use coroutines (Dispatchers.IO) or callbacks. -
Handler Leaks: Avoid non-static inner classes holding implicit reference to outer
Activity.
Lifecycle Awareness
-
Cancel Requests: In
ViewModel.onCleared()orActivity.onDestroy().override fun onCleared() { job.cancel() // for coroutines call?.cancel() // for Retrofit Call } -
Configuration Changes: Use
ViewModelto retain data across rotation.
Memory Leaks
-
Context Leaks: Don’t store
Activitycontext in static fields or long-lived objects. -
WebSocket/Call Leaks: Close in
onDestroy().
Connectivity Problems
-
Timeouts: Set reasonable
connectTimeout/readTimeout(e.g., 10–15 sec). -
Retry Strategy: Exponential backoff:
$$ delay = baseDelay \times 2^{retryCount} $$
\boxed{delay = baseDelay \times 2^{retryCount}}
-
Distinguish Errors:
-
IOException→ No network/server unreachable. -
HttpException→ Server responded with error code (4xx/5xx).
-
[!TIP] Golden Rule: Always handle
IOExceptionandHttpExceptionseparately. UseConnectivityManagerto pre-check network.