HTTP API를 코틀린 인터페이스로 선언한다. OkHttp 위에서 돈다.
interface UserApi {
@GET("users")
suspend fun getUsers(@Query("page") page: Int): List<UserDto>
@GET("users/{id}")
suspend fun getUser(@Path("id") id: Long): UserDto
@POST("users")
suspend fun create(@Body body: CreateUserDto): UserDto
@Multipart @POST("upload")
suspend fun upload(@Part file: MultipartBody.Part): UploadResponse
}
val retrofit = Retrofit.Builder()
.baseUrl("https://api.example.com/") // ★ 끝에 / 가 없으면 예외
.client(okHttpClient)
.addConverterFactory(json.asConverterFactory("application/json".toMediaType()))
.build()
val api = retrofit.create(UserApi::class.java)
suspend가 main-safe다
suspend 함수로 선언하면 Retrofit 이 내부에서 자체 스레드로 옮긴다
- withContext(Dispatchers.IO) 로 감쌀 필요가 없다
- 메인 스레드에서 그냥 불러도 안전하다
응답을 어떻게 받나
suspend fun getUser(id: Long): UserDto // 실패 시 예외
suspend fun getUser(id: Long): Response<UserDto> // 상태 코드·헤더를 직접 본다
val res = api.getUser(id)
if (res.isSuccessful) res.body() else when (res.code()) {
401 -> throw Unauthorized()
404 -> null
else -> throw ServerError(res.code())
}
예외의 종류를 구분한다
-
IOException — 네트워크 자체 실패 (연결 없음 · 타임아웃) → 재시도가 의미 있다
-
HttpException — 4xx/5xx (Response 를 안 쓸 때) → 코드에 따라 다르다
-
SerializationException — 응답 스키마 불일치 → 재시도해도 소용없다
-
전부 catch (e: Exception) 으로 뭉치면 재시도 정책을 세울 수 없다
직렬화 — kotlinx.serialization
@Serializable
data class UserDto(
val id: Long,
@SerialName("full_name") val fullName: String,
val email: String? = null // 기본값을 주면 필드가 없어도 파싱된다
)
val json = Json { ignoreUnknownKeys = true; coerceInputValues = true }
ignoreUnknownKeys = true 가 사실상 필수다
- 서버가 필드를 추가하는 순간 구버전 앱이 전부 파싱 예외로 죽는 것을 막는다
면접 함정
- ❌
baseUrl끝에/를 안 붙인다 →IllegalArgumentException이 즉시 난다. - ❌ "Retrofit이 재시도를 해 준다" → 하지 않는다. OkHttp의
retryOnConnectionFailure는 연결 실패만 다룬다.