Skip to content

Commit 487d9b0

Browse files
committed
Refactor: Retrofit ResultCallAdapter 도입을 통한 API 응답 처리 로직 개선
1 parent 94278b1 commit 487d9b0

4 files changed

Lines changed: 142 additions & 69 deletions

File tree

app/src/main/java/com/threegap/bitnagil/di/core/NetworkModule.kt

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import com.threegap.bitnagil.network.Kakao
1010
import com.threegap.bitnagil.network.NoneAuth
1111
import com.threegap.bitnagil.network.auth.AuthInterceptor
1212
import com.threegap.bitnagil.network.auth.TokenAuthenticator
13+
import com.threegap.bitnagil.network.calladapter.ResultCallAdapterFactory
1314
import com.threegap.bitnagil.network.token.ReissueService
1415
import com.threegap.bitnagil.network.token.TokenProvider
1516
import dagger.Module
@@ -45,7 +46,7 @@ object NetworkModule {
4546
fun provideJson(): Json =
4647
Json {
4748
ignoreUnknownKeys = true
48-
prettyPrint = true
49+
prettyPrint = BuildConfig.DEBUG
4950
explicitNulls = false
5051
}
5152

@@ -54,6 +55,10 @@ object NetworkModule {
5455
fun provideJsonConverter(json: Json): Converter.Factory =
5556
json.asConverterFactory(APPLICATION_JSON.toMediaType())
5657

58+
@Provides
59+
@Singleton
60+
fun provideResultCallAdapterFactory(): ResultCallAdapterFactory = ResultCallAdapterFactory()
61+
5762
@Provides
5863
@Singleton
5964
fun provideHttpLoggingInterceptor(): HttpLoggingInterceptor =
@@ -172,10 +177,12 @@ object NetworkModule {
172177
fun provideAuthRetrofit(
173178
baseUrl: String,
174179
converterFactory: Converter.Factory,
180+
resultCallAdapterFactory: ResultCallAdapterFactory,
175181
@Auth okHttpClient: OkHttpClient,
176182
): Retrofit = Retrofit.Builder()
177183
.baseUrl(baseUrl)
178184
.addConverterFactory(converterFactory)
185+
.addCallAdapterFactory(resultCallAdapterFactory)
179186
.client(okHttpClient)
180187
.build()
181188

@@ -185,10 +192,12 @@ object NetworkModule {
185192
fun provideNoneAuthRetrofit(
186193
baseUrl: String,
187194
converterFactory: Converter.Factory,
195+
resultCallAdapterFactory: ResultCallAdapterFactory,
188196
@NoneAuth okHttpClient: OkHttpClient,
189197
): Retrofit = Retrofit.Builder()
190198
.baseUrl(baseUrl)
191199
.addConverterFactory(converterFactory)
200+
.addCallAdapterFactory(resultCallAdapterFactory)
192201
.client(okHttpClient)
193202
.build()
194203
}
Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
package com.threegap.bitnagil.network.calladapter
2+
3+
import com.threegap.bitnagil.network.model.BaseResponse
4+
import com.threegap.bitnagil.network.model.ErrorResponse
5+
import okhttp3.Request
6+
import okhttp3.ResponseBody
7+
import okio.Timeout
8+
import retrofit2.Call
9+
import retrofit2.Callback
10+
import retrofit2.Converter
11+
import retrofit2.Response
12+
import kotlin.coroutines.cancellation.CancellationException
13+
14+
class ResultCall<T>(
15+
private val delegate: Call<BaseResponse<T>>,
16+
private val isUnit: Boolean,
17+
private val errorBodyConverter: Converter<ResponseBody, ErrorResponse>,
18+
) : Call<Result<T>> {
19+
20+
override fun enqueue(callback: Callback<Result<T>>) {
21+
delegate.enqueue(
22+
object : Callback<BaseResponse<T>> {
23+
override fun onResponse(call: Call<BaseResponse<T>>, response: Response<BaseResponse<T>>) {
24+
val result = if (response.isSuccessful) {
25+
val body = response.body()
26+
when {
27+
isUnit -> {
28+
@Suppress("UNCHECKED_CAST")
29+
Result.success(Unit as T)
30+
}
31+
32+
body?.data != null -> Result.success(body.data)
33+
else -> Result.failure(
34+
ApiException(
35+
code = "EMPTY_DATA: ${response.code()}",
36+
message = body?.message ?: "Response body or data is null",
37+
),
38+
)
39+
}
40+
} else {
41+
val errorResponse = parseErrorResponse(response.errorBody())
42+
Result.failure(
43+
ApiException(
44+
code = errorResponse?.code ?: "HTTP_${response.code()}",
45+
message = errorResponse?.message ?: response.message(),
46+
),
47+
)
48+
}
49+
callback.onResponse(this@ResultCall, Response.success(result))
50+
}
51+
52+
override fun onFailure(call: Call<BaseResponse<T>>, t: Throwable) {
53+
if (t is CancellationException) return
54+
55+
callback.onResponse(this@ResultCall, Response.success(Result.failure(t)))
56+
}
57+
},
58+
)
59+
}
60+
61+
private fun parseErrorResponse(errorBody: ResponseBody?): ErrorResponse? =
62+
errorBody?.let { body ->
63+
try {
64+
errorBodyConverter.convert(body)
65+
} catch (e: Exception) {
66+
null
67+
}
68+
}
69+
70+
override fun clone(): Call<Result<T>> = ResultCall(delegate.clone(), isUnit, errorBodyConverter)
71+
override fun execute(): Response<Result<T>> = throw UnsupportedOperationException("ResultCall doesn't support execute()")
72+
override fun isExecuted(): Boolean = delegate.isExecuted
73+
override fun cancel() = delegate.cancel()
74+
override fun isCanceled(): Boolean = delegate.isCanceled
75+
override fun request(): Request = delegate.request()
76+
override fun timeout(): Timeout = delegate.timeout()
77+
}
78+
79+
class ApiException(val code: String, override val message: String) : Exception(message)
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
package com.threegap.bitnagil.network.calladapter
2+
3+
import com.threegap.bitnagil.network.model.BaseResponse
4+
import com.threegap.bitnagil.network.model.ErrorResponse
5+
import okhttp3.ResponseBody
6+
import retrofit2.Call
7+
import retrofit2.CallAdapter
8+
import retrofit2.Converter
9+
import retrofit2.Retrofit
10+
import java.lang.reflect.ParameterizedType
11+
import java.lang.reflect.Type
12+
13+
class ResultCallAdapterFactory : CallAdapter.Factory() {
14+
15+
override fun get(
16+
returnType: Type,
17+
annotations: Array<out Annotation>,
18+
retrofit: Retrofit,
19+
): CallAdapter<*, *>? {
20+
if (getRawType(returnType) != Call::class.java) return null
21+
check(returnType is ParameterizedType) { "Return type must be parameterized as Call<Result<Foo>>" }
22+
23+
val responseType = getParameterUpperBound(0, returnType)
24+
25+
if (getRawType(responseType) != Result::class.java) return null
26+
check(responseType is ParameterizedType) { "Response must be parameterized as Result<Foo>" }
27+
28+
val successType = getParameterUpperBound(0, responseType)
29+
30+
val baseResponseType = object : ParameterizedType {
31+
override fun getRawType(): Type = BaseResponse::class.java
32+
override fun getOwnerType(): Type? = null
33+
override fun getActualTypeArguments(): Array<Type> = arrayOf(successType)
34+
}
35+
36+
val isUnit = getRawType(successType) == Unit::class.java
37+
38+
val errorBodyConverter: Converter<ResponseBody, ErrorResponse> =
39+
retrofit.responseBodyConverter(ErrorResponse::class.java, annotations)
40+
41+
return ResultCallAdapter<Any>(baseResponseType, isUnit, errorBodyConverter)
42+
}
43+
}
44+
45+
class ResultCallAdapter<T>(
46+
private val responseType: Type,
47+
private val isUnit: Boolean,
48+
private val errorBodyConverter: Converter<ResponseBody, ErrorResponse>,
49+
) : CallAdapter<BaseResponse<T>, Call<Result<T>>> {
50+
override fun responseType(): Type = responseType
51+
override fun adapt(call: Call<BaseResponse<T>>): Call<Result<T>> =
52+
ResultCall(call, isUnit, errorBodyConverter)
53+
}

data/src/main/java/com/threegap/bitnagil/data/common/SafeApiCall.kt

Lines changed: 0 additions & 68 deletions
This file was deleted.

0 commit comments

Comments
 (0)