반응형
안드로이드 앱에서 네트워크 요청은 흔하면서도 실수하기 쉬운 영역입니다. Retrofit과 Kotlin Coroutines를 조합하면 간결하고 안전한 네트워크 계층을 만들 수 있습니다. 이번 글에서는 핵심 개념과 실무 예제, 에러 처리 및 테스트 팁까지 정리해 보겠습니다.
1. 기본 구성 (Retrofit + Coroutines)
Retrofit을 Coroutine과 함께 쓰려면 suspend 함수를 선언하는 방식이 가장 깔끔합니다.
필수 의존성은 Retrofit, OkHttp, kotlinx-coroutines, 그리고 Gson/Moshi 같은 JSON 파서가 있습니다.
Gradle 의존성 예
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-gson:2.9.0")
implementation("com.squareup.okhttp3:okhttp:4.9.3")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.0")
API 인터페이스
interface ApiService {
@GET("users/{id}")
suspend fun getUser(@Path("id") id: String): UserDto
@POST("posts")
suspend fun createPost(@Body request: PostRequest): PostResponse
}
Retrofit 인스턴스 생성
fun provideRetrofit(): Retrofit {
val client = OkHttpClient.Builder()
.callTimeout(30, TimeUnit.SECONDS)
.addInterceptor(HttpLoggingInterceptor().apply {
level = HttpLoggingInterceptor.Level.BASIC
})
.build()
return Retrofit.Builder()
.baseUrl("https://api.example.com/")
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
}
2. 안전한 호출: Result 래핑과 예외 처리
네트워크 호출은 실패할 가능성이 항상 있으므로, 결과를 Result 또는 커스텀 sealed class로 감싸 처리하는 것이 좋습니다.
NetworkResult 유틸 예시
sealed class NetworkResult<out T> {
data class Success<T>(val data: T): NetworkResult<T>()
data class Error(val exception: Throwable, val code: Int? = null): NetworkResult<Nothing>()
}
suspend fun <T> safeApiCall(call: suspend () -> T): NetworkResult<T> {
return try {
val resp = call()
NetworkResult.Success(resp)
} catch (e: HttpException) {
NetworkResult.Error(e, e.code())
} catch (e: IOException) {
NetworkResult.Error(e)
} catch (e: Exception) {
NetworkResult.Error(e)
}
}
사용 예
class UserRepository(private val api: ApiService) {
suspend fun fetchUser(id: String): NetworkResult<UserDto> {
return safeApiCall { api.getUser(id) }
}
}
3. 리프레시 토큰/인증 처리 전략
401 응답이 오면 토큰 갱신을 진행해야 합니다.
OkHttp Interceptor에서 중앙화하면 코드 관리가 훨씬 수월합니다.
AuthInterceptor 예시
class AuthInterceptor(
private val tokenProvider: TokenProvider,
private val refreshApi: AuthApi
): Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request().newBuilder()
.addHeader("Authorization", "Bearer ${tokenProvider.accessToken}")
.build()
var response = chain.proceed(request)
if (response.code == 401) {
synchronized(this) {
val newToken = runBlocking { tokenProvider.refreshTokenIfNeeded(refreshApi) }
request = request.newBuilder()
.header("Authorization", "Bearer $newToken")
.build()
response.close()
response = chain.proceed(request)
}
}
return response
}
}
4. 테스트와 모킹
Retrofit 인터페이스는 쉽게 모킹할 수 있습니다.
코루틴 기반 함수는 runBlocking 또는 코루틴 테스트 라이브러리를 활용합니다.
테스트 예시
@Test
fun fetchUser_success_returnsUser() = runBlocking {
val mockApi = mock<ApiService>()
val expected = UserDto("1", "Alice")
whenever(mockApi.getUser("1")).thenReturn(expected)
val repo = UserRepository(mockApi)
val result = repo.fetchUser("1")
assertTrue(result is NetworkResult.Success)
assertEquals(expected, (result as NetworkResult.Success).data)
}
5. 정리
- Retrofit + Coroutines 조합은 코드 가독성과 생산성을 크게 높인다.
- 예외 처리를
Result형태로 통일하면 UI 계층에서 다루기 편하다. - 토큰 갱신은 Interceptor를 활용해 중앙화하자.
- 테스트는 인터페이스 모킹과 코루틴 테스트 라이브러리를 이용하자.
마무리: 안드로이드 네트워크 계층을 Compose 시대에 맞게 단순화하려면, Retrofit + Coroutines는 사실상 표준입니다.
반응형
'개발일기' 카테고리의 다른 글
| OkHttp Interceptor를 활용한 공통 에러 처리 & 로깅 전략 (4) | 2025.09.02 |
|---|---|
| Retrofit + Kotlin Flow로 실시간 데이터 처리 (4) | 2025.09.01 |
| 안드로이드 Compose vs 기존 XML UI 비교 (5) | 2025.08.28 |
| Kotlin Multiplatform (KMP) 소개와 활용 사례 (6) | 2025.08.27 |
| Kotlin DSL과 Gradle 스크립트 활용법 (3) | 2025.08.26 |