Retrofit turns an HTTP API into a type-safe Kotlin interface:

interface ApiService {
    @GET("users/{id}")
    suspend fun fetchUser(@Path("id") id: Int): User

    @GET("users")
    suspend fun fetchAllUsers(): List<User>
}

val retrofit = Retrofit.Builder()
    .baseUrl("https://api.example.com/")
    .addConverterFactory(MoshiConverterFactory.create())
    .build()

val apiService = retrofit.create(ApiService::class.java)

Combining this with the Repository pattern from Module 3 and error handling with a sealed class gives you a robust, testable data layer:

class UserRepository(private val api: ApiService, private val dao: UserDao) {
    suspend fun getUser(id: Int): NetworkResult<User> {
        return try {
            val user = api.fetchUser(id)
            dao.insertUser(user)
            NetworkResult.Success(user)
        } catch (e: IOException) {
            // No internet connection — fall back to cached data if we have it
            dao.getUserById(id)?.let { NetworkResult.Success(it) }
                ?: NetworkResult.Error("No connection and no cached data")
        } catch (e: HttpException) {
            NetworkResult.Error("Server error: ${e.code()}")
        }
    }
}

Catching IOException and HttpException separately matters: the first means “no network,” where falling back to cache is reasonable; the second means “the server responded, but with an error,” where a cache fallback usually isn’t appropriate.