Phase 4 · Data & PersistenceModule 42~32 min read

Optional · Networking with Retrofit

Call REST APIs and parse JSON with Retrofit and coroutines — connecting your app to the world.

Optional further study

You can complete every required capstone without this topic. Return after the core phase lessons when you want to explore it.

What you'll learn

Most apps talk to a server — a weather feed, a product catalog, a social API. Retrofit is the standard Android library for calling REST APIs: you describe the endpoints as a Kotlin interface, and it handles the HTTP and JSON for you. Combined with coroutines, a network call is just a suspend function.

By the end you'll be able to:

  • Understand REST endpoints and JSON
  • Model API responses as serializable data classes
  • Declare an API with Retrofit annotations
  • Build Retrofit and handle success and error responses

REST & JSON

A REST API exposes data at URLs (endpoints) you interact with using HTTP methods: GET to read, POST to create, PUT/PATCH to update, DELETE to remove. The server replies with JSON — human-readable text of objects and arrays — plus a status code (200 OK, 404 Not Found, 500 Server Error). Your job: send the right request and turn the JSON reply into Kotlin objects.

Data models

Create a data class that mirrors the JSON. Mark it @Serializable so kotlinx.serialization can parse it, and use @SerialName when a JSON field name doesn't match your Kotlin property. These network classes are often suffixed Dto (Data Transfer Object):

ArticleDto.kt
// Mirror the JSON shape. @Serializable enables kotlinx parsing.
@Serializable
data class ArticleDto(
    val id: Int,
    val title: String,
    @SerialName("published_at") val publishedAt: String,   // map a JSON name
)
Focused example: keep your generated package and theme. Add the shown APIs to your Compose project using Android Studio imports.

Tip

Keep network DTOs separate from your app's domain models, and map between them. The API's shape can be messy or change; a mapping function is your buffer, so a server tweak doesn't ripple through your whole app.

The API interface

Describe each endpoint as a suspend function annotated with its HTTP method and path. @Path fills in a URL segment; @Query adds a ?key=value parameter. Retrofit implements the interface for you:

NewsApi.kt
interface NewsApi {
    @GET("v2/top-headlines")
    suspend fun getHeadlines(
        @Query("country") country: String = "us",
    ): List<ArticleDto>

    @GET("v2/articles/{id}")
    suspend fun getArticle(@Path("id") id: Int): ArticleDto
}
Focused example: keep your generated package and theme. Add the shown APIs to your Compose project using Android Studio imports.

Building Retrofit

Configure Retrofit once with a baseUrl and a converter that knows how to parse JSON, then create your API. Setting ignoreUnknownKeys = true makes parsing resilient — the app won't crash when the server adds a field you don't model:

Network.kt
private val json = Json { ignoreUnknownKeys = true }   // don't crash on extra fields

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
    .build()

val api: NewsApi = retrofit.create(NewsApi::class.java)
Focused example: keep your generated package and theme. Add the shown APIs to your Compose project using Android Studio imports.

Calling & handling errors

Networks fail — no signal, timeouts, server errors — so every call needs error handling. Wrap it in try/catch and return a Result (or your own sealed outcome type) so the ViewModel can turn failures into a friendly UI state rather than a crash:

NewsRepository.kt
suspend fun fetchHeadlines(): Result<List<Article>> =
    try {
        val dtos = api.getHeadlines()          // non-blocking Retrofit suspend call
        Result.success(dtos.map { it.toDomain() })   // map DTO → your model
    } catch (e: IOException) {
        Result.failure(e)                      // no connection / timeout
    } catch (e: HttpException) {
        Result.failure(e)                      // 4xx / 5xx response
    }
Focused example: keep your generated package and theme. Add the shown APIs to your Compose project using Android Studio imports.

Watch out

Two things every real app needs: add the INTERNET permission in the manifest (network calls can fail with a permission error without it), and never assume a call succeeds. Common exceptions are IOException (no connection) and HttpException (a non-2xx response).

Practice: one small working feature

Describe a small API response with a DTO and call it through a suspend Retrofit service.

Check your result

A successful response becomes visible data; HTTP and connectivity failures become readable errors.

Remember

This lesson needs the declared dependencies, internet permission, and a suitable endpoint. Never put privileged server credentials in an app.

Recap & quick check

Key takeaways

  • REST APIs expose data at URL endpoints using HTTP methods (GET/POST/…), replying with JSON and a status code.
  • Model responses as @Serializable data classes (DTOs); use @SerialName to map mismatched field names.
  • Declare endpoints as suspend functions with @GET/@POST, @Path, and @Query annotations.
  • Build Retrofit once with a baseUrl and a JSON converter; ignoreUnknownKeys makes parsing resilient.
  • Always handle IOException and HttpException; map DTOs to domain models and return a Result.

Quick check

1. Which HTTP method reads data?

2. How do you declare a Retrofit endpoint that fetches headlines?

3. Why keep DTOs separate from domain models?

4. What does ignoreUnknownKeys = true do?

5. Which exceptions should you catch around a Retrofit call?

You can read local and remote data. The final Phase 4 piece ties them together cleanly — the repository pattern.