Designing the Networking Layer
The network is the app’s lifeline — and its weakest link
Most apps live and die by their backend communication. But on mobile the network is unreliable: slow, intermittent, sometimes absent. A well-designed networking layer hides this messiness behind a clean API and handles failure gracefully, so the rest of the app doesn’t have to.
Layered structure
The app talks only to repositories; repositories use an API service built on an HTTP client. The UI never touches raw HTTP.
REST vs GraphQL
- REST — simple, cacheable, the default. You may over-fetch (get fields you don’t need) or under-fetch (need multiple calls).
- GraphQL — the client asks for exactly the fields it needs in one query. Great for complex, data-heavy screens; more setup.
For mobile, minimising round-trips and payload size matters (battery, data, latency), which is why GraphQL or well-designed REST endpoints (returning screen-shaped data) are valued.
Handling failure: the core skill
Networks fail constantly. Your layer must:
- Time out sensibly (don’t hang forever).
- Retry transient failures — with exponential backoff (wait 1s, 2s, 4s…) so you don’t hammer the server.
- Map errors — turn HTTP/network errors into clear app-level results (no internet, server error, unauthorized).
sealed interface ApiResult<out T> {
data class Success<T>(val data: T) : ApiResult<T>
data class Error(val type: ErrorType, val message: String) : ApiResult<Nothing>
}
suspend fun <T> safeCall(block: suspend () -> T): ApiResult<T> = try {
ApiResult.Success(block())
} catch (e: IOException) {
ApiResult.Error(ErrorType.NETWORK, "No internet")
} catch (e: HttpException) {
ApiResult.Error(ErrorType.SERVER, "Server error ${e.code()}")
}
Cross-cutting concerns with interceptors
An interceptor (OkHttp) or middleware sits in the request pipeline to handle things centrally: attaching auth tokens, logging, adding headers, and refreshing expired tokens. This keeps that logic out of every call.
Other essentials
- Serialization — convert JSON to typed models; tolerate unknown fields so the app doesn’t break when the API adds fields.
- Cancellation — cancel in-flight requests when the user leaves the screen (coroutines/structured concurrency do this).
- Caching — HTTP caching and your own cache (next lessons) reduce calls.
- Pagination — never fetch everything at once (its own lesson).
Common mistakes
- Calling the network directly from the UI with no repository abstraction.
- No retries/backoff, or retrying non-idempotent requests unsafely.
- Surfacing raw HTTP errors to users instead of clear messages.
- Not cancelling requests when the screen goes away.
Summary: Wrap client–server communication in a layered design (repository → API service → HTTP client). Handle the unreliable network with timeouts, retries with backoff, and clear error mapping; centralise auth/logging in interceptors; tolerate API changes in serialization; and cancel requests when no longer needed.