Skip to content

Commit fc720d5

Browse files
authored
Merge pull request #383 from Runnect/bugfix/replace-gson-with-kotlinx
fix: R8 난독화 시 Retrofit API 호출 실패 해결
2 parents 689e086 + 126e99f commit fc720d5

4 files changed

Lines changed: 41 additions & 52 deletions

File tree

app/proguard-rules.pro

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -12,13 +12,13 @@
1212
-keep public class * extends java.lang.Exception
1313

1414
# --- DTO ---
15-
# Gson 리플렉션으로 역직렬화되는 DTO 클래스의 필드명 보존
16-
# (BaseResponse, ErrorResponse 등이 Gson과 Kotlin Serialization 양쪽에서 사용됨)
15+
# RetrofitV2/RetrofitFlow가 GsonConverterFactory를 사용하므로
16+
# Gson 리플렉션으로 직렬화되는 DTO 필드명 보존 필요
1717
-keepclassmembers class com.runnect.runnect.data.dto.** { <fields>; }
1818

1919
# --- Retrofit + kotlin.Result ---
20-
# Retrofit이 리턴 타입의 제네릭 정보를 리플렉션으로 읽으므로 Signature 유지 필요
21-
# kotlin.Result는 inline class라 R8이 타입 정보를 최적화할 수 있음
20+
# kotlin.Result는 inline class라 R8이 제네릭 타입 정보를 최적화함
21+
# Retrofit이 Call<Result<T>>의 타입 파라미터를 리플렉션으로 읽지 못해 CallAdapter 생성 실패
2222
# https://github.com/square/retrofit/issues/3880
2323
-keep class kotlin.Result { *; }
2424
-keepattributes Signature

app/src/main/java/com/runnect/runnect/data/network/calladapter/ResultCall.kt

Lines changed: 13 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
package com.runnect.runnect.data.network.calladapter
22

3-
import com.google.gson.Gson
4-
import com.runnect.runnect.data.dto.response.base.ErrorResponse
53
import com.runnect.runnect.domain.common.RunnectException
4+
import kotlinx.serialization.json.Json
5+
import kotlinx.serialization.json.int
6+
import kotlinx.serialization.json.jsonObject
7+
import kotlinx.serialization.json.jsonPrimitive
68
import retrofit2.Call
79
import retrofit2.Callback
810
import retrofit2.Response
@@ -11,7 +13,7 @@ import okio.Timeout
1113

1214
class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> {
1315

14-
private val gson = Gson()
16+
private val json = Json { ignoreUnknownKeys = true }
1517

1618
override fun execute(): Response<Result<T>> {
1719
throw UnsupportedOperationException("ResultCall doesn't support execute")
@@ -46,23 +48,17 @@ class ResultCall<T>(private val call: Call<T>) : Call<Result<T>> {
4648
}
4749

4850
private fun parseErrorResponse(response: Response<*>): RunnectException {
49-
val errorJson = response.errorBody()?.string()
51+
val errorString = response.errorBody()?.string()
5052

5153
return runCatching {
52-
val errorBody = gson.fromJson(errorJson, ErrorResponse::class.java)
53-
val message = errorBody?.run {
54-
message ?: error ?: ERROR_MSG_COMMON
55-
}
56-
57-
RunnectException(
58-
code = errorBody.status,
59-
message = message
60-
)
54+
val jsonObject = json.parseToJsonElement(errorString.orEmpty()).jsonObject
55+
val status = jsonObject["status"]?.jsonPrimitive?.int ?: response.code()
56+
val message = jsonObject["message"]?.jsonPrimitive?.content
57+
?: jsonObject["error"]?.jsonPrimitive?.content
58+
?: ERROR_MSG_COMMON
59+
RunnectException(code = status, message = message)
6160
}.getOrElse {
62-
RunnectException(
63-
code = response.code(),
64-
message = ERROR_MSG_COMMON
65-
)
61+
RunnectException(code = response.code(), message = ERROR_MSG_COMMON)
6662
}
6763
}
6864

app/src/main/java/com/runnect/runnect/data/network/calladapter/flow/FlowCallAdapter.kt

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
package com.runnect.runnect.data.network.calladapter.flow
22

3-
import com.google.gson.Gson
4-
import com.runnect.runnect.data.dto.response.base.ErrorResponse
53
import com.runnect.runnect.domain.common.RunnectException
64
import kotlinx.coroutines.flow.Flow
75
import kotlinx.coroutines.flow.flow
86
import kotlinx.coroutines.suspendCancellableCoroutine
7+
import kotlinx.serialization.json.Json
8+
import kotlinx.serialization.json.jsonObject
9+
import kotlinx.serialization.json.jsonPrimitive
910
import retrofit2.Call
1011
import retrofit2.CallAdapter
1112
import retrofit2.Callback
@@ -18,7 +19,7 @@ class FlowCallAdapter<T>(
1819
private val responseType: Type
1920
) : CallAdapter<T, Flow<Result<T>>> {
2021

21-
private val gson = Gson()
22+
private val json = Json { ignoreUnknownKeys = true }
2223
override fun responseType() = responseType
2324

2425
// Retrofit의 Call을 Result<>로 변환
@@ -58,15 +59,18 @@ class FlowCallAdapter<T>(
5859
} ?: Result.failure(nullBodyException)
5960
}
6061

61-
// Response에서 오류를 파싱하여 RunnectException 객체를 생성
6262
private fun parseErrorResponse(response: Response<*>): RunnectException {
6363
val errorBodyString = response.errorBody()?.string()
64-
val errorResponse = errorBodyString?.let {
65-
gson.fromJson(it, ErrorResponse::class.java)
66-
}
6764

68-
val errorMessage = errorResponse?.message ?: errorResponse?.error ?: ERROR_MSG_COMMON
69-
return RunnectException(response.code(), errorMessage)
65+
return runCatching {
66+
val jsonObject = json.parseToJsonElement(errorBodyString.orEmpty()).jsonObject
67+
val message = jsonObject["message"]?.jsonPrimitive?.content
68+
?: jsonObject["error"]?.jsonPrimitive?.content
69+
?: ERROR_MSG_COMMON
70+
RunnectException(response.code(), message)
71+
}.getOrElse {
72+
RunnectException(response.code(), ERROR_MSG_COMMON)
73+
}
7074
}
7175

7276
companion object {
Lines changed: 11 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,8 @@
11
package com.runnect.runnect.data.network.interceptor
22

3-
import com.google.gson.Gson
4-
import com.google.gson.JsonElement
5-
import com.google.gson.JsonObject
6-
import com.google.gson.JsonParseException
7-
import com.google.gson.JsonSyntaxException
8-
import com.runnect.runnect.data.dto.response.base.BaseResponse
3+
import kotlinx.serialization.json.Json
4+
import kotlinx.serialization.json.JsonObject
5+
import kotlinx.serialization.json.jsonObject
96
import okhttp3.Interceptor
107
import okhttp3.MediaType.Companion.toMediaTypeOrNull
118
import okhttp3.Response
@@ -18,14 +15,14 @@ import timber.log.Timber
1815
*/
1916
class ResponseInterceptor : Interceptor {
2017

21-
private val gson = Gson()
18+
private val json = Json { ignoreUnknownKeys = true }
2219

2320
override fun intercept(chain: Interceptor.Chain): Response {
2421
val originalResponse = chain.proceed(chain.request())
2522
if (!originalResponse.isSuccessful) return originalResponse
2623

2724
val bodyString = originalResponse.peekBody(Long.MAX_VALUE).string()
28-
val newResponseBodyString = jsonToBaseResponse(bodyString)?.let {
25+
val newResponseBodyString = extractData(bodyString)?.let {
2926
it.toResponseBody("application/json".toMediaTypeOrNull())
3027
} ?: return originalResponse
3128

@@ -42,26 +39,18 @@ class ResponseInterceptor : Interceptor {
4239
}
4340
}
4441

45-
private fun jsonToBaseResponse(body: String): String? {
42+
private fun extractData(body: String): String? {
4643
return try {
47-
val jsonElement: JsonElement = gson.fromJson(body, JsonElement::class.java)
48-
if (!isBaseResponse(jsonElement.asJsonObject)) {
49-
return null
50-
}
51-
52-
val baseResponse = gson.fromJson(body, BaseResponse::class.java)
53-
gson.toJson(baseResponse.data)
54-
} catch (e: JsonSyntaxException) {
55-
null // JSON 구문 분석 오류 발생 시 원래 형식을 반환
56-
} catch (e: JsonParseException) {
57-
null // JSON 파싱 오류 발생 시 원래 형식을 반환
44+
val jsonObject = json.parseToJsonElement(body).jsonObject
45+
if (!isBaseResponse(jsonObject)) return null
46+
jsonObject["data"].toString()
5847
} catch (e: Exception) {
59-
null // 기타 예외 발생 시 원래 형식을 반환
48+
null
6049
}
6150
}
6251

6352
private fun isBaseResponse(jsonObject: JsonObject): Boolean {
6453
val requiredFields = listOf("status", "success", "message", "data")
65-
return requiredFields.all { jsonObject.has(it) }
54+
return requiredFields.all { it in jsonObject }
6655
}
6756
}

0 commit comments

Comments
 (0)