Course outline

Android development / Intermediate: state, data, and inclusive features / Lesson 9

Networking and repository boundaries

Convert unreliable remote responses into validated local behavior without blocking the UI or hiding failures.

About 4 min readContent reviewed
On this page

Lesson goals

  • Define transport, mapping, and repository responsibilities.
  • Choose timeout, retry, and cancellation policies by operation semantics.

Start with a fallible boundary

A remote request can fail before connection, during transport, with an HTTP error, while decoding, or after receiving semantically invalid content. These are different outcomes, even if a screen eventually offers one retry button. Define a service contract separately from the repository that validates and stores its results. The UI should not know which HTTP library performs the call. Use HTTPS and a configured client with explicit timeouts; never weaken certificate validation simply to make a development server appear to work.

Provide access without blocking rendering

Declare the INTERNET permission at the manifest root for network access. It is a normal permission, not a dangerous runtime prompt like camera access. A network library with suspend support may already manage I/O correctly; wrapping every call blindly in another dispatcher is not a substitute for reading its contract. If using blocking platform APIs, put the blocking portion on an injected I/O dispatcher and always close resources. Cancellation support depends on the client implementation, so test it rather than assuming the suspend modifier automatically interrupts sockets.

xml Merge these attributes into the app manifest
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <uses-permission android:name="android.permission.INTERNET" />
    <application android:usesCleartextTraffic="false" />
</manifest>

Retain existing activities, theme, and application attributes; this reduced file is explanatory. The cleartext policy reinforces HTTPS intent but does not replace server authentication or careful per-domain network-security configuration. Do not overwrite the complete manifest with this excerpt.

Validate transport models before storage

A JSON field can be absent or contain an invalid value even when the HTTP status is successful. Model transport uncertainty separately from the trusted domain. Map responses through validation before replacing cached content, and consider whether one invalid row should reject the batch or be reported individually. Preserve stable ids and server version information when synchronization depends on them. Do not pass raw DTOs through every composable: doing so spreads backend assumptions through the UI and makes schema changes unexpectedly expensive.

kotlin A transport boundary independent of an HTTP library
data class RemoteBook(val id: String?, val title: String?)
data class Book(val id: String, val title: String)

fun RemoteBook.toDomain(): Book? {
    val validId = id?.takeIf { it.isNotBlank() } ?: return null
    val validTitle = title?.trim()?.takeIf { it.isNotEmpty() } ?: return null
    return Book(validId, validTitle)
}

interface BookService { suspend fun fetchBooks(): List<RemoteBook> }

The configured HTTP client implements BookService and owns decoding and status handling. A repository calls toDomain before committing data, and reports invalid records deliberately. No real endpoint or fabricated network result is assumed by this excerpt.

Retry according to meaning

A retry repeats an operation whose previous outcome may be unknown. A read is often safe to retry with bounded backoff, but a create or payment can duplicate side effects unless the server supports an idempotency key or equivalent contract. Distinguish authentication failure from a temporary timeout; repeatedly retrying invalid credentials only adds load. Honor relevant server retry guidance and avoid synchronized retry storms. Show users whether content is cached, refreshing, or unavailable, and preserve useful cached data during a recoverable refresh error instead of blanking the entire screen.

Test outcomes rather than a live service

Use a fake service or controlled local test server to return delayed responses, malformed bodies, HTTP failures, and reversed completion order. This makes repository behavior repeatable without relying on public connectivity. Redact tokens, personal content, and sensitive query parameters from logs. Connection availability is only a hint: a connected device may face a captive portal or a failing backend. Treat the actual request result as authoritative, and avoid a separate online check that promises success before the request has even started.

Common errors and fixes

A successful HTTP response crashes mapping

Why it happens
The payload violates assumed field requirements.
How to fix it
Validate DTOs and return an explicit data-contract failure.
Prevent it next time
Test missing, null, and malformed response fields.

Retry creates duplicate records

Why it happens
A write was repeated after an ambiguous timeout.
How to fix it
Use a stable operation key with server-enforced deduplication.
Prevent it next time
Define write idempotency before enabling automatic retries.

Online detection says yes but requests fail

Why it happens
Network presence was treated as service reachability.
How to fix it
Handle request failures and present cached content where possible.
Prevent it next time
Test captive-portal and unavailable-server scenarios.

Check your understanding

1. A timed-out create request may already have reached the server. Explain why a new random id on retry is unsafe.

Show worked solution

A new id makes the retry look like a different operation, so the server can create a duplicate. Retain the original operation identity across retries and require the backend to return the established result for that key. The client must also preserve that identity across process recreation if the operation remains pending.

2. A refresh fails while a valid cached list exists. Specify screen state and a repository test.

Show worked solution

Keep the list visible and expose refresh failure separately with a retry action and appropriate freshness information. In a test, seed cached records, make the fake service throw an I/O failure, refresh, and assert records remain intact while the failure status changes. No real network should be required.

Key takeaways

  • Transport success does not establish domain validity.
  • Retries require an explicit side-effect contract.
  • Repositories protect the UI from transport details and instability.

Official references