Skip to content
IT-606 · Android Programming/Quick Revision Short Notes

Android Programming (IT-606) - Unit 4 Short Notes

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" in AndroidManifest.xml or 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 in AndroidManifest.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 ConnectivityManager to 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.NetworkCallback or use LiveData with NetworkCallback in a ViewModel.

[!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:

    • 2xx Success: 200 OK, 201 Created

    • 4xx Client Error: 400 Bad Request, 401 Unauthorized, 404 Not Found

    • 5xx Server 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 Accept header; 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)

    • @Body for 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..299 range.

    • response.body(): Parsed data (null if error).

    • response.errorBody(): Raw error response (e.g., { "error": "Invalid" }).

  • Global Error Handling: Use Interceptor or 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.Factory for 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.isSuccessful before accessing body(). Use converter-gson for JSON; Moshi is an alternative.


5. Coroutines for Asynchronous Networking

Coroutine Fundamentals

  • launch: Fire-and-forget, returns Job.

  • async: Returns Deferred<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: Around suspend calls.

  • CoroutineExceptionHandler: For uncaught exceptions in launch.

  • Result wrapper: 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/combine for streams.

[!TIP] Exam Focus: viewModelScope auto-cancels on ViewModel clearance. Never use GlobalScope in 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 null safety better.

    
    val moshi = Moshi.Builder().add(KotlinJsonAdapterFactory()).build()
    
    val adapter = moshi.adapter(User::class.java)
    
    

Advanced Parsing

  • Polymorphic Deserialization: Use @JsonAdapter with custom JsonReader logic.

  • Date/Time: Register JavaTimeAdapter for LocalDate, 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) or DataStore (preferred).

    • Android Keystore: For highly sensitive tokens.

OAuth 2.0 Flows

  • Authorization Code Flow with PKCE: Standard for mobile.

    • Generate code_verifier and code_challenge.

    • Use AppAuth library: 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. Use EncryptedSharedPreferences or 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, Expires headers.

  • 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 onClosed with exponential backoff.

File Uploads/Downloads

  • Multipart Upload (Retrofit):

    
    @Multipart
    
    @POST("upload")
    
    suspend fun uploadFile(@Part file: MultipartBody.Part): Response<UploadResponse>
    
    
  • Progress Listener: Wrap ResponseBody to 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) or onDestroy() (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 TestCoroutineDispatcher or StandardTestDispatcher.

Instrumentation Tests

  • Run on device/emulator with androidTest source set.

  • Use Espresso for UI, WorkManagerTestInitHelper for 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://inspect for 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.DEBUG guards.

  • Secure Storage: EncryptedSharedPreferences or Android Keystore for 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() or Activity.onDestroy().

    
    override fun onCleared() {
    
        job.cancel() // for coroutines
    
        call?.cancel() // for Retrofit Call
    
    }
    
    
  • Configuration Changes: Use ViewModel to retain data across rotation.

Memory Leaks

  • Context Leaks: Don’t store Activity context 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 IOException and HttpException separately. Use ConnectivityManager to pre-check network.

Go to where you left off?

Quick Add to Notes

Save questions, your own notes and screenshots into notes filed by unit. It takes a free account.

Create free account

Have an account? Log in