개요
- 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을 반환한다.
구현
- CallAdapter의 responseType을 어떻게 구현할 지
- 응답 결과를 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'>인스턴스에게 작업을 위임한다.