← All courses

Designing the Networking Layer

🗓 May 31, 2026 ⏱ 3 min read

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

Repository (clean API for the app) API service (endpoints + models) HTTP client (Retrofit/Ktor/URLSession)

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.