일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 | 29 |
30 | 31 |
- 클린아키텍처
- 커스텀뷰
- NavHost
- 안드로이드
- 뷰
- coroutine
- 알고리즘
- 로그인
- Flow
- Compose
- 회원가입
- Build variants
- NavController
- cleanarchitecture
- 컴포즈
- Jetpack
- 코틀린
- 코딩테스트
- 플레이스토어
- 파이어베이스
- 리사이클러뷰
- XML
- UiState
- sharedFlow
- Android
- ListAdapter
- Authentication
- DiffUtil
- Kotlin
- MVVM
- Today
- Total
Grusie 안드로이드 개발 기술 블로그
[Android] CallAdapter로 레트로핏 에러 처리하기 본문
늘 Coroutine을 사용하여 suspend함수로 레트로핏의 결과를 반환해주고, try-catch {}로 묶어서 에러 핸들링을 하였다.
그러던 와중 CallAdapter라는 클래스를 알게 되어서 적어보려고 한다.
Retrofit2 빌더 패턴에서, 확장함수를 보다보면, addCallAdapterFactory() 라는 메서드가 존재한다.
응답을 받았을 때, callAdapterFactory에서 원하는 타입으로 리턴해주는 형태로 작업을 하기에, 코틀린의 Result를 사용하거나 임의로 만든 sealed class를 사용할 수 있을 것 같다.
CallAdapter를 사용했을 경우 통신에 대한 try-catch{} 지옥을 겪을 일이 없어 코드가 깔끔해진다.
사용법
CallAdapter
class CustomCallAdapter<T>(
private val successType: Type,
) : CallAdapter<T, Call<Result<T>>> {
override fun responseType(): Type = successType
override fun adapt(call: Call<T>): Call<Result<T>> {
return CustomCall(call)
}
}
CallAdapter를 상속받는 CustomCallAdapter를 생성해준다.
타입은 들어올 때의 타입 <T> 그리고 나갈 때의 타입인 <Call<Result<T>>로 선언해주었다.
adapt()에서, CustomCall 클래스에 Call<T>를 넘겨 Call<Result<T>>로 변환해준다.
CallAdapterFactory
class CustomCallAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<out Annotation>,
retrofit: Retrofit
): CallAdapter<*, *>? {
//리턴타입이 Call형태가 아닐 경우
if(getRawType(returnType) != Call::class.java) {
return null
}
//리턴타입이 제네릭 인자를 가지는지 확인
check(returnType is ParameterizedType) {
"return type must be parameterized as Call<Result<T>>"
}
val responseType = getParameterUpperBound(0, returnType)
if(getRawType(responseType) != Result::class.java) {
return null
}
//Result 클래스가 제네릭 인자를 가지는지 확인
check(responseType is ParameterizedType) {
"response type must be parameterized as Call<Result<T>>"
}
//Result의 제네릭 인자를 얻어 CallAdapter를 생성한다.
val successBodyType = getParameterUpperBound(0, responseType)
return CustomCallAdapter<Any>(successBodyType)
}
}
callAdapter를 생성해줄 callAdapterFactory클래스이다.
들어온 타입의 바운드 타입과 제네릭 타입의 존재 여부와 타입 일치 여부를 판별 후 callAdapter에 매개변수로 넘겨 생성한다.
Call
class CustomCall<T>(
private val delegate : Call<T>
) : Call<Result<T>>{
override fun clone(): Call<Result<T>> = CustomCall(delegate.clone())
//클론
override fun execute(): Response<Result<T>> = throw UnsupportedOperationException("CustomCall doesn't support execute")
//execute는 보통 막아둠
override fun isExecuted(): Boolean = delegate.isExecuted
override fun cancel() = delegate.cancel()
override fun isCanceled(): Boolean = delegate.isCanceled
override fun request(): Request = delegate.request()
override fun timeout(): Timeout = delegate.timeout()
override fun enqueue(callback: Callback<Result<T>>) {
delegate.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
if(response.isSuccessful) {
response.body()?.let {
callback.onResponse(
this@CustomCall,
Response.success(Result.success(it))
)
//데이터 응답을 성공했으면, success로 변경
} ?: callback.onResponse(
this@CustomCall,
Response.success(Result.failure(ErrorState.Error(RequestState.NullError))),
)
}
}
override fun onFailure(call: Call<T>, t: Throwable) {
val fetchState = when (t) {
is SocketException -> RequestState.NetworkError
is HttpException -> RequestState.ParsingError
else -> RequestState.UnknownError
}
//데이터 응답을 실패했을 때, 커스텀 생성한 리퀘스트 스테이트 변경
callback.onResponse(
this@CustomCall,
Response.success(Result.failure(ErrorState.Error(fetchState))),
)
//그에 해당하는 에러코드를 넘겨줌
}
})
}
}
서버통신을 실행시켜, 돌아오는 데이터를 기반으로 원하는 요청 값으로 변경하여 리턴하는 Call Class이다.
Call 클래스를 상속받으면, 오버라이딩 해야할 메서드가 많으나, enqueue를 제외하고는, 기존꺼를 그대로 사용하거나, execute같은 경우에는 막아두는 형태로 사용한다.
RetrofitModule
@Singleton
@Provides
fun provideRetrofit(okHttpClient: OkHttpClient): Retrofit {
return Retrofit.Builder()
.baseUrl(BuildConfig.BASE_URL)
.addCallAdapterFactory(CustomCallAdapterFactory())
.addConverterFactory(GsonConverterFactory.create())
.client(okHttpClient)
.build()
}
레트로핏을 생성하는 부분에 addCallAdapterFactory에 CallAdapter를 생성하는 Factory클래스를 넣어주면 사용 할 수 있다.
에러가 발생했을 때, Result.failure를 리턴하도록 처리해두었으니, 코드에서 해당하는 에러 처리를 진행하면 될 것 같다.
참고
https://velog.io/@suev72/AndroidRetrofit-Call-adapter
[Android/Retrofit] Call adapter - 이해/개발
은 HTTP API를 별도 조작 없이 쉽게 응답을 객체로 변환해주는 라이브러리이다. 코틀린을 사용한다면 API 호출 시 내부적으로 요청이 이루어져서 따로 콜백을 정의할 필요없이 응답객체를 받을 수
velog.io
https://pluu.github.io/category/#io23
Pluu Dev - Categories
[Android] 기상청 프로그램 v2.1.0 Posted on 20 Apr 2015 [Android] 기상청 프로그램 v2.0.0 Posted on 16 Apr 2015 [Windows Phone] Pluu Google Reader 기동 시작!!! Posted on 27 Apr 2012 [Android] 기상청 프로그램 v1.1.1 Posted on 16 Apr 2
pluu.github.io
Retrofit에 CallAdapter를 적용하는 법
개요
medium.com
후기
이런 걸 이제야 알게 되었다는 게 너무나도 아쉬웠다.
직접 찾아서 레트로핏을 사용할 때에는 이런 정보를 얻지 못하다가, 우연히 코드를 보게 되어 공부하게 된 것인데, 미리 알았더라면 좋았을 것 같다.
이렇게 처음에는 알지 못했고 직접 찾아보지 않으면 모르는 기술들이 몇 개가 있었다. 대표적으로 diffUtils이 있었다.
이럴 때마다 사수의 부재가 크다는 생각을 하고, 좋은 코드를 더 많이 보고싶다는 생각을 한다.
특히.. 테스트 코드에 대한 것은 찾아봐도 잘 모르겠다.
'안드로이드 개발' 카테고리의 다른 글
[Android] release 버전에서 Gson 파싱이 안 되던 오류(코드 난독화 문제) (0) | 2024.05.07 |
---|---|
[Android] 플레이 스토어 버전 업데이트 관리하기 (1) | 2024.05.07 |
[Android] 리사이클러뷰 DiffUtil 사용법 및 단일 체크 처리하기 (0) | 2024.04.30 |
[Android] 버전 카탈로그로 Gradle 의존성 관리하기 (0) | 2024.04.24 |
[Android] 테스트 코드 작성하기(단위테스트 - unit test) (1) | 2024.04.03 |