[Android/Retrofit] Call adapter - 이해/개발
CallAdapter를 쓰게 된 배경..!
retrofit은 HTTP API를 별도 조작 없이 쉽게 응답을 객체로 변환해주는 라이브러리이다. 코틀린을 사용한다면 API 호출 시 내부적으로 요청이 이루어져서 따로 콜백을 정의할 필요없이 응답객체를 받을 수 있다.
그러나 만약 API호출 시 에러가 발생하거나, 기대하지 않는 응답코드가 올 경우 처리하는 경우 매 호출마다 try-catch 예외 처리 지옥에 빠질 수 있다!!!!
→ 원하는 응답으로 변경하기 위해, 에러핸들링 하기 위해 CallAdapter를 적용했다.
개발하깅🤓
1. CallAdapter
Call → T타입으로 변환해주는 인터페이스, CallAdapter.Factory에 의해 인스턴스가 생성된다.
class NetworkResponseAdapter<T>(
private val successType: Type,
) : CallAdapter<T, Call<NetworkResponse<T>>> { // 여기서 <앞, 뒤> 에 넣어준 것에 따라
override fun responseType(): Type = successType
override fun adapt(call: Call<T>): Call<NetworkResponse<T>> { // in(Call<앞>), out(Call<뒤>)의 타입이 정해짐
return NetworkResponseCall(call) // 얘는 커스텀으로 구현한 클래스로 아래에 나옴!
}
}
- responseType : 어댑터가 HTTP 응답을 객체로 변환할 때, 반환값으로 지정할 타입을 리턴하는 메소드. 예를 들어 Call에 대한 responseType의 반환값은 Repo에 대한 타입이다.
- adapt : 메소드의 파라미터로 받은 call에게 작업을 위임하는 T타입 인스턴스를 반환하는 메소드
2. CallAdapter.Factory
위 CallAdapter의 인스턴스를 생성하는 팩토리 클래스.
get()의 첫번째 인자 returnType에 서비스 메소드의 리턴 타입이 전달된다.
class NetworkResponseAdapterFactory : CallAdapter.Factory() {
override fun get(
returnType: Type,
annotations: Array<Annotation>,
retrofit: Retrofit,
): CallAdapter<*, *>? {
// suspend functions wrap the response type in `Call`
// returnType이 Call로 감싸져 있는지?
if (Call::class.java != getRawType(returnType)) {
return null
}
// check first that the return type is `ParameterizedType`
// returnType이 제네릭 인자를 가지는지? Call<NetworkResponse<<Foo>> or Call<NetworkResponse<out Foo>>
check(returnType is ParameterizedType) {
"return type must be parameterized as Call<NetworkResponse<<Foo>> or Call<NetworkResponse<out Foo>>"
}
// get the response type inside the `Call` type
// returnType에서 첫번째 제네릭 인자를 얻는다. NetworkResponse<out Foo>
val responseType = getParameterUpperBound(0, returnType)
// if the response type is not ApiResponse then we can't handle this type, so we return null
// 기대한 것처럼 동작하기 위해서는 추출한 제네릭 인자가 내가 만든 NetworkResponse타입이어야함.
if (getRawType(responseType) != NetworkResponse::class.java) {
return null
}
// the response type is ApiResponse and should be parameterized
// 제네릭 인자 가지는지 확인 NetworkResponse<Foo> or NetworkResponse<out Foo>
check(responseType is ParameterizedType) { "Response must be parameterized as NetworkResponse<Foo> or NetworkResponse<out Foo>" }
// Foo를 얻어서 CallAdapter를 생성한다.
val successBodyType = getParameterUpperBound(0, responseType)
return NetworkResponseAdapter<Any>(successBodyType)
}
}
팩토리는 세개의 메소드를 가진다.
- get: 파라미터로 받은 returnType과 동일한 타입을 반환하는 서비스 메서드에 대한 CallAdapter 인스턴스를 반환한다.
- getParameterUpperBound: type의 index 위치의 제네릭 파라미터에 대한 upper bound type을 반환한다. 예를 들어 getParameterUpperBound(1, Map<String, ? extends Runnable>)은 Runnable Type을 반환한다.
- getRawType: type의 raw type을 반환한다. (raw type: 제네릭 파라미터가 생략된 타입. List<? extends Runnable>의 raw type은 List를 말한다.)
😲자자, 이게 제일 중요합니다용 여기를 잘 바까야대용!!😲
3. 응답 결과를 NetworkResponse로 감싸기 위해 NetworkResponseCall 생성
: 여기서 NetworkResponseCall의 enqueue()를 호출 시 인자로 받아온 Call<>의 enqueue를 호출하여 이 결과에 따라 wrapping작업을 하게 된다.
→ 바로바로 이 과정에서 에러핸들링을 하는 것!!!! (내가 사용하는 sealed class(여기서는 NetworkResponse)로 래핑하면서 data의 에러핸들링을 내가 원하는 기준으로 쓸 수 있다는것이지용)
보면 아래 작업에서 response가 isSuccessful할 때 말고도 전부 Response.success()로 보내고 있다.
왜냐면 우리는 모든 경우를 NetworkResponse로 래핑해서 사용하고 싶으니깐!!
class NetworkResponseCall<T>(
private val delegate: Call<T>,
) : Call<NetworkResponse<T>> {
override fun enqueue(callback: Callback<NetworkResponse<T>>) {
return delegate.enqueue(object : Callback<T> {
override fun onResponse(call: Call<T>, response: Response<T>) {
val body = response.body()
val error = response.errorBody()
if (response.isSuccessful) {
if (body != null) {
if((body as BaseResponse<*>).dataHeader.result == "SUCCESS"){
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Success(body))
)
} else {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Error(COMMON_RESULT_FAIL_ERROR, "result fail"))
)
}
} else {
// Response is successful but the body is null
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Error(NULL_BODY_ERROR, "null body"))
)
}
} else {
val errorBody = when {
error == null -> null
error.contentLength() == 0L -> null
else -> try {
error
} catch (ex: Exception) {
null
}
}
if (errorBody != null) {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Error(API_ERROR, errorBody.toString()))
)
} else {
callback.onResponse(
this@NetworkResponseCall,
Response.success(NetworkResponse.Error(UNKNOWN_ERROR,
"unknown exception occurred"))
)
}
}
}
override fun onFailure(call: Call<T>, throwable: Throwable) {
val networkResponse: NetworkResponse<T> = when (throwable) {
is IOException -> NetworkResponse.Error(CONNECTION_ERROR,
throwable.message ?: "io exception occurred")
else -> NetworkResponse.Error(UNKNOWN_ERROR,
throwable.message ?: "unknown exception occurred")
}
callback.onResponse(this@NetworkResponseCall, Response.success(networkResponse))
}
})
}
override fun isExecuted() = delegate.isExecuted
override fun clone() = NetworkResponseCall(delegate.clone())
override fun isCanceled() = delegate.isCanceled
override fun cancel() = delegate.cancel()
override fun execute(): Response<NetworkResponse<T>> {
throw UnsupportedOperationException("NetworkResponseCall doesn't support execute")
}
override fun request(): Request = delegate.request()
override fun timeout(): Timeout = delegate.timeout()
}
실제 개발하면서 에러핸들링 된 일.
현재 통신을 하는데에 있어 kotlinx-serialization을 사용중이다. Gson대신 kotlinx-serialization로 갈아탄 이유에서 말했듯이
data class Person(val name: String)
에서 {"nick":"sangeun"}이런식으로 해당 키(name)가 없으면 에러를 뱉고 앱이 죽는다. (디폴트밸류가 있으면 됨)
근데, 위의 예외처리로 인해 앱이 죽지않고 에러를 뱉음~~
Ref. [https://medium.com/shdev/retrofit에-calladapter를-적용하는-법-853652179b5b](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)
완전히 도움이 되어버림.
Author And Source
이 문제에 관하여([Android/Retrofit] Call adapter - 이해/개발), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://velog.io/@suev72/AndroidRetrofit-Call-adapter저자 귀속: 원작자 정보가 원작자 URL에 포함되어 있으며 저작권은 원작자 소유입니다.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)