Networking with Retrofit
๐ Networking with Retrofit
๐ก Defining an API Interface
There's no implementation here at all โ ApiService is just an interface with annotated method signatures; Retrofit generates the actual networking code behind it. suspend fun means each call is a genuine Kotlin Intermediate Chapter 1 suspend function โ no callbacks, no manual thread-switching, and the JSON response comes back already converted into the declared return type.
๐ JSON Conversion โ Gson or Moshi
Retrofit itself doesn't parse JSON โ a converter library does, plugged in as a converter factory:
Gson vs Moshi
| Gson | Moshi | |
|---|---|---|
| Reflection-based | Yes | Optional โ can use compile-time code generation instead |
| Kotlin null-safety awareness | Limited โ can silently produce nulls for non-null types | Better โ respects Kotlin nullability more strictly |
| Renaming a JSON field | @SerializedName("email_address") | @Json(name = "email_address") |
Either works and both are widely used in real projects; Moshi is generally the more actively recommended choice for new Kotlin code specifically because of its stricter null-safety behavior, which matters given how central null safety is to the rest of this curriculum (Kotlin Fundamentals Chapter 3).
๐๏ธ Building the Retrofit Instance
retrofit.create(ApiService::class.java) is Kotlin Intermediate Chapter 6's reflection in action, though wrapped away inside Retrofit's own library code โ it dynamically generates a real implementation of the ApiService interface at runtime, wiring each annotated method to an actual HTTP call. Like the database singleton pattern from last chapter, one shared Retrofit instance (and one shared ApiService) is normal for an entire app, not recreated per screen.
โ ๏ธ Error Handling โ Reusing a Familiar Sealed Class
Kotlin Intermediate Chapter 2 introduced exactly this shape for a different reason (handling a Flow's possible states) โ the same sealed class pattern is the standard way to represent "a network call that might succeed, fail, or still be in progress":
NetworkResult<out T> uses Kotlin Intermediate Chapter 3's covariance โ out T is what allows Error and Loading to both implement NetworkResult<Nothing> while still being treated as a NetworkResult<List<User>> (or any other T) where needed, since they never actually need to hold a real T value. IOException generally means "never reached the server" (no connection, DNS failure); HttpException means "reached the server, got back an error status code" โ distinguishing them gives a more useful error message than one generic catch-all.
๐ฒ Loading States in the ViewModel
This connects directly to Chapter 2's single-UiState-per-screen pattern:
Every earlier architectural piece shows up together here: init { } (Kotlin Fundamentals Chapter 4) triggers the initial load, viewModelScope.launch (this chapter's Retrofit calls are suspend functions, needing a coroutine), and the exhaustive when (Kotlin Fundamentals Chapter 5's sealed class matching) ensures every possible NetworkResult case updates the UI state correctly.
Retrofit vs JavaScript fetch/axios
| JavaScript (fetch/axios) | Retrofit | |
|---|---|---|
| Defining an endpoint | A URL string passed to fetch()/axios.get() | An annotated interface method |
| JSON parsing | response.json() (manual, untyped by default) | Automatic, into a typed data class (Gson/Moshi) |
| Async model | Promises / async-await | Kotlin suspend functions |
| Error handling | try/catch around fetch, checking response.ok | try/catch around the suspend call, per-exception-type |
๐ป Coding Challenges
Challenge 1: An API Interface
Define a data class Post(val id: Int, val title: String, val body: String) and an interface PostApiService with a suspend getPosts(): List<Post> GET endpoint and a suspend getPost(id: Int): Post GET endpoint using @Path. Build a Retrofit instance and PostApiService for https://jsonplaceholder.typicode.com/ using a converter factory of your choice.
Goal: Practice defining a Retrofit API interface and building the client instance.
Challenge 2: NetworkResult Error Handling
Write a suspend fun fetchPosts(api: PostApiService): NetworkResult<List<Post>> using the chapter's NetworkResult sealed class, catching both IOException and HttpException with distinct error messages.
Goal: Practice the try/catch-into-sealed-class error wrapping pattern.
Challenge 3: A Full ViewModel with Loading State
Write a PostsUiState data class (isLoading, posts, errorMessage) and a PostsViewModel that loads posts on init using Challenge 2's fetchPosts(), updating uiState correctly through loading, success, and error paths using an exhaustive when.
Goal: Practice the complete Retrofit-to-UiState pipeline end to end.
Suspend functions, sealed classes, covariance, data class copy(), reflection, property delegation โ every one of them from earlier chapters, recombined for a new purpose. Networking in a real Android app isn't a separate skill so much as it's these same tools applied to yet another data source, joining local storage (Room, last chapter) as another Flow/StateFlow feeding the same UI pattern.
๐ฏ What's Next
Next chapter: Dependency Injection with Hilt โ why DI matters, Hilt setup, modules, and injecting into a ViewModel.