Grusie 안드로이드 개발 기술 블로그

[Android] CallAdapter로 레트로핏 에러 처리하기 본문

안드로이드 개발

[Android] CallAdapter로 레트로핏 에러 처리하기

grusie 2024. 4. 25. 16:04
728x90
반응형
SMALL

늘 Coroutine을 사용하여 suspend함수로 레트로핏의 결과를 반환해주고, try-catch {}로 묶어서 에러 핸들링을 하였다.
그러던 와중 CallAdapter라는 클래스를 알게 되어서 적어보려고 한다.


Retrofit2 빌더 패턴에서, 확장함수를 보다보면, addCallAdapterFactory() 라는 메서드가 존재한다.

CallAdapter 사용법


응답을 받았을 때, 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

https://medium.com/shdev/retrofit%EC%97%90-calladapter%EB%A5%BC-%EC%A0%81%EC%9A%A9%ED%95%98%EB%8A%94-%EB%B2%95-853652179b5b

 

Retrofit에 CallAdapter를 적용하는 법

개요

medium.com

 

후기

 

이런 걸 이제야 알게 되었다는 게 너무나도 아쉬웠다.

직접 찾아서 레트로핏을 사용할 때에는 이런 정보를 얻지 못하다가, 우연히 코드를 보게 되어 공부하게 된 것인데, 미리 알았더라면 좋았을 것 같다.

 

이렇게 처음에는 알지 못했고 직접 찾아보지 않으면 모르는 기술들이 몇 개가 있었다. 대표적으로 diffUtils이 있었다.


이럴 때마다 사수의 부재가 크다는 생각을 하고, 좋은 코드를 더 많이 보고싶다는 생각을 한다.
특히.. 테스트 코드에 대한 것은 찾아봐도 잘 모르겠다.

반응형
LIST