Skip to content

Commit

Permalink
[#37] Retrofit CallAdapter, Gson Converter 커스텀
Browse files Browse the repository at this point in the history
- 리모트 푸시를 위한 커밋
  • Loading branch information
heechokim committed May 31, 2022
1 parent d87d9e3 commit f5a60d7
Show file tree
Hide file tree
Showing 8 changed files with 134 additions and 87 deletions.
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
package com.moyerun.moyeorun_android.common.exceptions

import com.moyerun.moyeorun_android.network.api.Error
import com.moyerun.moyeorun_android.network.api.ApiFailure

class ApiException(val url: String, val error: Error) : RuntimeException() {
class ApiException(val url: String, val apiFailure: ApiFailure) : RuntimeException() {
val case: String
get() = error.case
get() = apiFailure.case

override val message: String?
get() = error.message
get() = apiFailure.message
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,10 @@ enum class ApiErrorCase(val case: String) {
return values().find { it.case == case } ?: UNKNOWN
}

fun getException(url: String, error: Error, cause: Throwable? = null): Throwable {
return when(findError(error.case)) {
fun getException(url: String, apiFailure: ApiFailure, cause: Throwable? = null): Throwable {
return when(findError(apiFailure.case)) {
NOT_LOGIN -> { TODO() }
else -> ApiException(url, error)
else -> ApiException(url, apiFailure)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ package com.moyerun.moyeorun_android.network.api
/**
* 서버 팀과 합의한 성공/실패 응답 값에 대한 모델입니다.
*/
data class Success<T>(
data class ApiSuccess<T>(
val data: T
)

data class Error(
data class ApiFailure(
val message: String?,
val case: String
)
Original file line number Diff line number Diff line change
@@ -1,5 +1,11 @@
package com.moyerun.moyeorun_android.network.api

import com.moyerun.moyeorun_android.network.calladapter.ApiResult
import com.moyerun.moyeorun_android.network.model.TestSuccess
import retrofit2.http.GET

interface ApiService {

@GET()
suspend fun getSuccessTest(): ApiResult<TestSuccess>
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package com.moyerun.moyeorun_android.network.calladapter

import com.moyerun.moyeorun_android.network.api.ApiFailure
import com.moyerun.moyeorun_android.network.api.ApiSuccess

/**
* Success : API 호출 성공 시, body를 Wrapping 합니다.
* Failure : API 호출 실패 시, throwable을 Wrpping 합니다.
*/
sealed class ApiResult<out T> {
data class Success<T>(val body: T) : ApiResult<T>()
data class Failure(val throwable: Throwable) : ApiResult<Nothing>()
sealed class ApiResult<T> {
data class Success<T>(val body: T) : ApiResult<ApiSuccess<T>>()
data class Failure<T>(val throwable: Throwable, val errorBody: T) : ApiResult<ApiFailure>()
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,92 +3,118 @@ package com.moyerun.moyeorun_android.network.calladapter
import com.google.gson.Gson
import com.moyerun.moyeorun_android.common.exceptions.NetworkException
import com.moyerun.moyeorun_android.network.api.ApiErrorCase
import com.moyerun.moyeorun_android.network.api.Error
import com.moyerun.moyeorun_android.network.api.ApiFailure
import okhttp3.Request
import okio.Timeout
import retrofit2.*

class ApiResultCall<S>(
private val delegate: Call<S>
) : Call<ApiResult<S>> {
override fun enqueue(callback: Callback<ApiResult<S>>) {
delegate.enqueue(object : Callback<S> {
override fun onResponse(call: Call<S>, response: Response<S>) {
class ApiResultCall<T>(
private val delegate: Call<T>
) : Call<ApiResult<T>> {

override fun enqueue(callback: Callback<ApiResult<T>>) {
delegate.enqueue(object : Callback<T> {
// 이거 그대로 오버라이딩하면 됨...
override fun onResponse(call: Call<T>, response: Response<T>) {
val requestUrl = delegate.request().url().toString()

// status code 200번대.
// status code가 200 ~ 299 일 때.
if (response.isSuccessful) {
val body = response.body()
if (body != null) {
callback.onResponse(
this@ApiResultCall,
Response.success(ApiResult.Success(body))
Response.success()
)
} else {
callback.onResponse(
this@ApiResultCall,
Response.success(
ApiResult.Failure(
NetworkException(
"Response from $requestUrl was null " +
"but response body type was decleared as non-null",
HttpException(response)
)
)
)
)
}
} else { // status code 200번대 아님.
val errorBody = response.errorBody()
if (errorBody != null) {
val errorResponse = Gson().fromJson(errorBody.string(), Error::class.java)
callback.onResponse(
this@ApiResultCall,
Response.success(
ApiResult.Failure(
ApiErrorCase.getException(
requestUrl,
errorResponse,
HttpException(response)
)
)
)
)
} else {
callback.onResponse(
this@ApiResultCall,
Response.success(
ApiResult.Failure(
NetworkException(
requestUrl,
HttpException(response)
)
)
)
)

}
}
}

override fun onFailure(call: Call<S>, throwable: Throwable) {
callback.onResponse(
this@ApiResultCall,
Response.success(
ApiResult.Failure(
NetworkException(
call.request().url().toString(),
throwable
)
)
)
)
override fun onFailure(call: Call<T>, t: Throwable) {
TODO("Not yet implemented")
}
})
}
// override fun enqueue(callback: Callback<ApiResult<T>>) {
// delegate.enqueue(object : Callback<S> {
// override fun onResponse(call: Call<S>, response: Response<S>) {
// val requestUrl = delegate.request().url().toString()
//
// // status code 200번대.
// if (response.isSuccessful) {
// val body = response.body()
// if (body != null) {
// callback.onResponse(
// this@ApiResultCall,
// Response.success(ApiResult.Success(body))
// )
// } else {
// callback.onResponse(
// this@ApiResultCall,
// Response.success(
// ApiResult.Failure(
// NetworkException(
// "Response from $requestUrl was null " +
// "but response body type was decleared as non-null",
// HttpException(response)
// )
// )
// )
// )
// }
// } else { // status code 200번대 아님.
// val errorBody = response.errorBody()
// if (errorBody != null) {
// val errorResponse = Gson().fromJson(errorBody.string(), ApiFailure::class.java)
// callback.onResponse(
// this@ApiResultCall,
// Response.success(
// ApiResult.Failure(
// ApiErrorCase.getException(
// requestUrl,
// errorResponse,
// HttpException(response)
// )
// )
// )
// )
// } else {
// callback.onResponse(
// this@ApiResultCall,
// Response.success(
// ApiResult.Failure(
// NetworkException(
// requestUrl,
// HttpException(response)
// )
// )
// )
// )
// }
// }
// }
//
// override fun onFailure(call: Call<S>, throwable: Throwable) {
// callback.onResponse(
// this@ApiResultCall,
// Response.success(
// ApiResult.Failure(
// NetworkException(
// call.request().url().toString(),
// throwable
// )
// )
// )
// )
// }
// })
// }
//
override fun clone(): Call<ApiResult<T>> = ApiResultCall(delegate.clone())

override fun clone(): Call<ApiResult<S>> = ApiResultCall(delegate.clone())

override fun execute(): Response<ApiResult<S>> {
override fun execute(): Response<ApiResult<T>> {
throw UnsupportedOperationException("ApiResultCall doesn't support execute")
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,31 +14,37 @@ class ApiResultCallAdapterFactory : CallAdapter.Factory() {
retrofit: Retrofit
): CallAdapter<*, *>? {

// suspend functions wrap the response type in `Call`
// suspend 함수의 리턴 타입은 `Call`로 Wrapping 되어 있어야 한다.
if (getRawType(returnType) != Call::class.java) {
return null
}

// check first that the return type is `ParameterizedType`
// suspend 함수의 리턴 타입은 제네릭 타입이어야 한다.
if (returnType !is ParameterizedType) {
return null
}

// get the response type inside the `Call` type
val responseType = getParameterUpperBound(0, returnType)
// `Call`로 Wrapping 되어 있는 ApiResult 타입을 추출한다.
val apiResultType = getParameterUpperBound(0, returnType)

// if the response type is not ApiResponse then we can't handle this type, so we return null
if (getRawType(responseType) != ApiResult::class.java) {
// ApiResult 타입은 `ApiResult`로 Wrapping 되어 있어야 한다.
if (getRawType(apiResultType) != ApiResult::class.java) {
return null
}

// the response type is ApiResponse and should be parameterized
if (responseType !is ParameterizedType) {
// ApiResult 타입은 제네릭 타입이어야 한다.
if (apiResultType !is ParameterizedType) {
return null
}

val successResponseType = getParameterUpperBound(0, responseType)
// `ApiResult`로 Wrapping 되어 있는 response 타입을 추출한다.
val responseType = getParameterUpperBound(0, apiResultType)

// response 타입은 제네릭 타입이어야 한다.
if (responseType !is ParameterizedType) {
return null
}

return ApiResultCallAdapter<Any>(successResponseType)
return ApiResultCallAdapter<Any>(responseType)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.moyerun.moyeorun_android.network.model

data class TestSuccess(
val id: Int,
val name: String
)

0 comments on commit f5a60d7

Please sign in to comment.