Retrofit Custom Call Adapter

최희창·2022년 7월 19일
0

REST에 대해

목록 보기
3/3

개요

  • Retrofit은 HTTP API에 대해 인터페이스를 사용하여 쉽게 용청을 보낼 수 있고 응답 결과를 자바 오브젝트로 변환해주는 라이브러리이다.
  • 그런데 여기서 내가 원하는 응답 결과로 변경하기 위해 Retrofit의 Call Adapter를 적용할 수 있다.

CallAdapter

  • Call<'R'>을 T 타입으로 변환해주는 인터페이스로 CallAdapter.Factory에 의해 인스턴스가 생성된다.
  • 2개의 메서드를 가진다.
//어댑터가 HTTP 응답을 자바 오브젝트로 변환할 때 반환값으로 지정할 타입을 리턴하는 메서드. 
//예를 들어 Call<Repo>에 대한 responseType의 반환값은 Repo에 대한 타입이다. 
Type responseType();

//메서드의 파라미터로 받은 call에게 작업을 위임하는 T 타입 인스턴스를 반환하는 메서드
T adapt(Call<R> call);

CallAdapter.Factory

  • CallAdapter의 인스턴스를 생성하는 팩토리 클래스로, Retrofit 서비스 메서드의 리턴 타입에 기반한 인스턴스를 생성한다.
  • 3개의 메서드를 가진다.
public abstract @Nullable CallAdapter<?, ?> get(
    Type returnType, Annotation[] annotations, Retrofit retrofit);

protected static Type getParameterUpperBound(int index, ParameterizedType type) {
  return Utils.getParameterUpperBound(index, type);
}

protected static Class<?> getRawType(Type type) {
  return Utils.getRawType(type);
}
  • get : 파라미터로 받은 returnType과 동일한 타입을 반환하는 서비스 메서드에 대한 CallAdapter 인스턴스를 반환한다.
  • getParameterUpperBound : type의 index 위치의 제네릭 파라미터에 대한 upper bound type을 반환한다.
  • getRawType : type의 raw type을 반환한다.

구현

  1. CallAdapter의 responseType을 어떻게 구현할 지
  2. 응답 결과를 Result로 어떻게 래핑할 지

Base

sealed class Result<out T : Any> {
    data class Success<T : Any>(val body: T?) : Result<T>()

    data class Failure(val code: Int, val error: String?) : Result<Nothing>()

    data class NetworkError(val exception: IOException) : Result<Nothing>()

    data class Unexpected(val t: Throwable?) : Result<Nothing>()
}
  • 이후 CallAdapter 인터페이스를 구현하는 클래스와 Factory를 정의한다.
  • R 타입의 응답을 Call<Result>로 매핑해야 하기 때문에 CallAdapter<R, Call<Result<'R'>>>을 구현하는 클래스를 정의했다.
class ResultCallAdapter<R: Any>(private val responseType: Type) : CallAdapter<R, Call<Result<R>>> {
    override fun responseType(): Type {
        return responseType
    }

    override fun adapt(call: Call<R>): Call<Result<R>> {
        TODO("Not yet implemented")
    }
    
    class Factory : CallAdapter.Factory() {
        override fun get(
            returnType: Type,
            annotations: Array<out Annotation>,
            retrofit: Retrofit
        ): CallAdapter<*, *>? {
            // 먼저 리턴 타입의 로우 타입이 Call인지 확인한다.
            if (getRawType(returnType) != Call::class.java) {
                return null
            }
            // 이후 리턴타입이 제네릭 인자를 가지는지 확인한다. 리턴 타입은 Call<?>가 돼야 한다.
            check(returnType is ParameterizedType) {
                "return type must be parameterized as Call<Result<Foo>> or Call<Result<out Foo>>"
            }
            // 리턴 타입에서 첫 번째 제네릭 인자를 얻는다.
            val responseType = getParameterUpperBound(0, returnType)
            
            // 기대한것 처럼 동작하기 위해선 추출한 제네릭 인자가 Result 타입이어야 한다.
            if (getRawType(responseType) != Result::class.java) {
                return null
            }
            
            // Result 클래스가 제네릭 인자를 가지는지 확인한다. 제네릭 인자로는 응답을 변환할 클래스를 받아야 한다.
            check(responseType is ParameterizedType) {
                "Response must be parameterized as Result<Foo> or Result<out Foo>"
            }
            
            // 마지막으로 Result의 제네릭 인자를 얻어서 CallAdapter를 생성한다.
            val successBodyType = getParameterUpperBound(0, responseType)

            return ResultCallAdapter<Any>(successBodyType)
        }
    }
}
  • 팩토리에서 responseType을 추출하고 추출한 Type 인스턴스로 CallAdapter를 생성하도록 작성되었다.

응답 결과를 Result로 래핑

  • CallAdapter의 adapt 메서드는 기존의 Call을 파라미터로 받아 Call<Result<'R'>>로 래핑한 인스턴스를 반환한다.
  • Custom Call을 정의
class ResultCall<T : Any>(private val call: Call<T>) : Call<Result<T>> {

    override fun clone(): Call<Result<T>> = ResultCall(call.clone())

    override fun execute(): Response<Result<T>> {
        throw UnsupportedOperationException("ResultCall doesn't support execute")
    }

    override fun enqueue(callback: Callback<Result<T>>) {
        call.enqueue(object : Callback<T> {
            override fun onResponse(call: Call<T>, response: Response<T>) {
                if (response.isSuccessful) {
                    callback.onResponse(
                        this@ResultCall,
                        Response.success(Result.Success(response.body()))
                    )
                } else {
                    callback.onResponse(
                        this@ResultCall,
                        Response.success(
                            Result.Failure(
                                response.code(),
                                response.errorBody()?.string()
                            )
                        )
                    )
                }
            }

            override fun onFailure(call: Call<T>, t: Throwable) {
                val networkResponse = when (t) {
                    is IOException -> Result.NetworkError(t)
                    else -> Result.Unexpected(t)
                }
                callback.onResponse(this@ResultCall, Response.success(networkResponse))
            }
        })
    }

    override fun isExecuted(): Boolean = call.isExecuted

    override fun cancel() = call.cancel()

    override fun isCanceled(): Boolean = call.isCanceled

    override fun request(): Request = call.request()

    override fun timeout(): Timeout = call.timeout()
}
  • Result Call의 핵심은 enqueue메서드이다. 나머지 메서드는 파라미터로 받은 기존의 Call<'R'>인스턴스에게 작업을 위임한다.
profile
heec.choi

0개의 댓글