πŸ’μ„œλ²„ 응닡 Cherry Pick!πŸ’ (OkHttp Interceptor)

MJ Studio
MJ Studio
Published in
14 min readJan 13, 2020

--

λŒ€λ‹€μˆ˜μ˜ 규λͺ¨κ°€ μžˆλŠ” μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜λ“€μ€ 자체 μ„œλ²„λ₯Ό μš΄μ˜ν•˜λ©° REST APIλ₯Ό 기반으둜 λ‘” μ„œλ²„ 톡신듀은 데이터λ₯Ό JSON의 ν˜•νƒœλ‘œ μ£Όκ³ λ°›μŠ΅λ‹ˆλ‹€. μ•ˆλ“œλ‘œμ΄λ“œμ˜ 개발자라면 이런 μ„œλ²„λ“€κ³Ό REST APIλ₯Ό μ΄μš©ν•œ 톡신을 ν•˜κΈ° μœ„ν•΄μ„œ Retrofit, OkHttp, Gsonκ³Ό 같은 라이브러리λ₯Ό ν•œ 번쯀 λ“€μ–΄λ³΄μ•˜κ±°λ‚˜ μ‚¬μš©ν•΄λ³΄μ•˜μ„ κ²ƒμž…λ‹ˆλ‹€.

이 ν¬μŠ€νŒ…μ€ μ΄λŸ¬ν•œ 톡신 μ½”λ“œλ₯Ό μž‘μ„±ν•˜λŠ” μƒν™©μ—μ„œ μ„œλ²„μ˜ JSON 응닡 쀑, OkHttp의 Interceptorλ₯Ό μ΄μš©ν•˜μ—¬ μš°λ¦¬κ°€ μ›ν•˜λŠ” 데이터 λΆ€λΆ„λ§Œ λ½‘μ•„μ„œ λ³„λ„μ˜ νŒŒμ‹± 없이 λ°”λ‘œ μ‚¬μš©ν•  수 μžˆλŠ” μ½”λ“œλ₯Ό 지 수 μžˆλŠ” 방법에 λŒ€ν•΄μ„œ λ‹€λ£Ήλ‹ˆλ‹€. 이 방법을 μ‚¬μš©ν•˜λ©΄ Retrofit μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν•˜λŠ” κ³Όμ •κ³Ό μ‚¬μš©ν•˜λŠ” 과정이 맀우 νŽΈλ¦¬ν•΄μ§‘λ‹ˆλ‹€!πŸ˜€

Problem : Boilerplate code😫

λ‹€μŒκ³Ό 같은 μ„œλ²„μ˜ 응닡을 μƒκ°ν•΄λ³΄κ² μŠ΅λ‹ˆλ‹€. μ„œλ²„μ—μ„œ 정상적인 응닡이 μ„±κ³΅ν•œ μƒν™©μž…λ‹ˆλ‹€.

{
"status": 200,
"success": true,
"message": "νŽ€λ”© 정보 쑰회 성곡",
"data": [
{
"funding_idx": 95,
"user_idx": 12,
"store_idx": 1,
"funding_money": 2400,
"reward_percent": 0,
"funding_time": "2019-12-30T04:44:55.000Z",
"reward_money": 0
},
{
"funding_idx": 87,
"user_idx": 12,
"store_idx": 6,
"funding_money": 2400,
"reward_percent": 0,
"funding_time": "2019-12-29T11:57:39.000Z",
"reward_money": 0
},
....
]
}

λ‹€μŒμ€ μ„œλ²„μ—μ„œ 정상적인 응닡이 μ‹€νŒ¨ν•œ κ²½μš°μž…λ‹ˆλ‹€.

{
"status": 400,
"success": false,
"message": "ν•΄λ‹Ήν•˜μ§€ μ•ŠλŠ” userIdxκ°’μž…λ‹ˆλ‹€."
}

μš°λ¦¬κ°€ μ΄λŸ¬ν•œ μ„œλ²„μ˜ 응닡을 Retrofit의 GsonConverterFactoryλ₯Ό μ΄μš©ν•΄μ„œ λ°›μœΌλ €λ©΄ μ–΄λ–»κ²Œ ν•΄μ•Ό ν• κΉŒμš”? 이상적인 상황이라면 μš°λ¦¬λŠ” 보톡 data에 ν•΄λ‹Ήν•˜λŠ” κ°μ²΄λ“€μ˜ 리슀트만 λ°›μ•„μ™€μ„œ UI에 보여주고 싢을 κ²ƒμž…λ‹ˆλ‹€. 그러면 λ‹€μŒκ³Ό 같은 μ½”ν‹€λ¦°μ˜ 데이터 클래슀λ₯Ό λ¨Όμ € μ •μ˜ν•©λ‹ˆλ‹€. 이λ₯Ό Fundingμ΄λΌλŠ” 데이터 클래슀둜 μ •μ˜ν–ˆμŠ΅λ‹ˆλ‹€.

Funding 데이터 클래슀

그리고 사싀상 ν•„μš” μ—†μ§€λ§Œ status, success, messageκ³Ό 응닡이 μ‹€νŒ¨ν•  μ‹œμ— dataκ°€ μ˜€μ§€ μ•ŠμœΌλ―€λ‘œ dataλ₯Ό nullable둜 μ •μ˜ν•œ ResponseWrapper 데이터 클래슀λ₯Ό ν•˜λ‚˜ μ •μ˜ν•©λ‹ˆλ‹€. μ΄λŠ” μ„œλ²„μ˜ 응닡이 λͺ¨λ‘ μœ„μ™€ 같은 ν˜•μ‹μ΄κΈ° λ•Œλ¬Έμ— λͺ¨λ“  μš”μ²­μ˜ 응닡에 계속 μ“°μ΄κ²Œ λ©λ‹ˆλ‹€.

ResponseWrapper 데이터 클래슀

그러면 λ‹€μŒκ³Ό 같이 Retrofit μΈν„°νŽ˜μ΄μŠ€λ₯Ό λ§Œλ“€ 수 μžˆμŠ΅λ‹ˆλ‹€.

@GET("mypage/fundlist")
fun listFundingHistories() : Call<ResponseWrapper<List<Funding>>>

그리고 λ‹€μŒκ³Ό 같이 μ½”λ“œμƒμ—μ„œ μ“Έ 수 μžˆμŠ΅λ‹ˆλ‹€.

Dirty Response Wrap!

μ„œλ²„λž‘ 톡신 ν•œλ²ˆ ν•˜κ² λ‹€λŠ”λ° 골 λ•Œλ¦¬λŠ” μ½”λ“œκ°€ 아닐 수 μ—†μŠ΅λ‹ˆλ‹€πŸ˜«. μ‹€μ œλ‘œ 쑰금 κ³Όμž₯ν•˜κΈ° μœ„ν•΄μ„œ μ½”λ“œλ₯Ό μœ„μ™€ 같이 λ§Œλ“€μ—ˆμ§€λ§Œ, μ™„μ „νžˆ 비합리적인 μ½”λ“œλŠ” μ•„λ‹™λ‹ˆλ‹€. μ—¬κΈ°μ„œ μ£Όμ„μœΌλ‘œ 숫자λ₯Ό 달아둔 λ„€νŠΈμ›Œν¬ μš”μ²­μ˜ μ‹€νŒ¨ μΌ€μ΄μŠ€μ— λŒ€ν•΄μ„œ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

  1. μ„œλ²„μ™€ 톡신이 μ•„μ˜ˆ μ‹€νŒ¨ν•œ μƒν™©μž…λ‹ˆλ‹€. μ„œλ²„κ°€ λ‹«ν˜€μžˆμ„ μˆ˜λ„ 있고, 404 Not Found μ—λŸ¬κ°€ 났을 μˆ˜λ„ 있고 TimeOut 일 μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€.
  2. Responseμ—μ„œ Codeκ°€ 200 이상 300미만이 μ•„λ‹Œ κ²½μš°μž…λ‹ˆλ‹€. REST APIμ—μ„œ λ…Όλ¦¬μ μœΌλ‘œ 성곡 μ‘λ‹΅μœΌλ‘œ κ°„μ£Όλ˜λŠ” Code의 λ²”μ£Ό μΈμ§€λ‘œ 응닡이 μ„±κ³΅ν–ˆλŠ”μ§€λ₯Ό κ²€μ‚¬ν•©λ‹ˆλ‹€.
  3. μ„œλ²„ κ°œλ°œμžκ°€ κ°œλ°œν•œ λ‘œμ§μƒμ—μ„œ μ‹€νŒ¨ν–ˆμ„ λ•Œμž…λ‹ˆλ‹€. 보톡 2번이 μ„±κ³΅ν–ˆλŠ”λ° 3번이 μ‹€νŒ¨ν•˜λŠ” κ²½μš°λŠ” μ„œλ²„ 개발자의 μ‹€μˆ˜κ°€ μ•„λ‹ˆλΌλ©΄ λ“œλ­…λ‹ˆλ‹€.
  4. μ„œλ²„μ—μ„œλ„ μ„±κ³΅ν–ˆλŠ”λ° 데이터가 μ—†λŠ” κ²½μš°μž…λ‹ˆλ‹€. 보톡 μ‘λ‹΅μ—μ„œλŠ” 데이터가 ν•„μš”ν•˜μ§€λ§Œ 응닡 데이터가 ν•„μš” 없이 μ½”λ“œλ§Œ 200~300으둜 μ˜€λŠ” 응닡도 μ’…μ’… 있기 λ•Œλ¬Έμ— 선택적인 검사 μ‚¬ν•­μž…λ‹ˆλ‹€. 보톡 2번이 μ„±κ³΅ν–ˆμ„ λ•Œ 4번이 μ‹€νŒ¨ν•˜λŠ” κ²½μš°λŠ” λ“œλ­…λ‹ˆλ‹€.

μ—¬κΈ°μ„œ 1,2λ²ˆμ€ Retrofit λΌμ΄λΈŒλŸ¬λ¦¬μ—μ„œ κ²°μ •ν•΄μ£ΌλŠ” μ‹€νŒ¨ 상황이고, 3,4λ²ˆμ€ μ„œλ²„ ν”„λ‘œν† μ½œμ΄ status, success, message 그리고 data둜 ν•œλ²ˆ μ‹Έμ—¬μžˆκΈ° λ•Œλ¬Έμ— μ–΄μ©” 수 없이 ν•œ 번 더 검사해야 ν•˜λŠ” μ‹€νŒ¨ μƒν™©μž…λ‹ˆλ‹€. λ¬Όλ‘  이λ₯Ό RxJavaλ‚˜ Kotlin Coroutine 그리고 ResponseWrapper에 λŒ€ν•œ ν™•μž₯ ν•¨μˆ˜λ₯Ό μ΄μš©ν•˜λ©΄ μ’€ 더 κ°„νŽΈν•˜κ²Œ 응닡을 μ²˜λ¦¬ν•  수 μžˆκ² μ§€λ§Œ, μ–΄μ°Œ λ˜μ—ˆλ“  μš°λ¦¬κ°€ μ„œλ²„μ˜ κ·œκ²©μ— λ§žμΆ°μ§„ 응닡 λ•Œλ¬Έμ— ResponseWrapperλž€ 녀석을 ν•œ κΊΌν’€ 벗겨내야 ν•œλ‹€λŠ” 것은 λ‹€λ¦„μ—†μŠ΅λ‹ˆλ‹€.

μ €λŠ” 보톡 μ„œλ²„ κ°œλ°œμžκ°€ μœ„μ™€ 같이 응닡을 λ³΄λ‚΄μ£ΌλŠ” 것을 μ„ ν˜Έν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€. status와 successλΌλŠ” 응닡 값은 μ‘λ‹΅μ˜ μ½”λ“œλ‘œ νŒλ‹¨μ„ν•˜λ©΄ λ˜λŠ” 일이고 messageλŠ” κ°„λ‹¨ν•œ μ—λŸ¬ 메세지λ₯Ό μ‚¬μš©μžμ—κ²Œ λ„μš°λŠ” 데 λ‹Ήμž₯은 μœ μš©ν•˜κ² μ§€λ§Œ, 앱이 ν™•μž₯되고 μ—¬λŸ¬ μ–Έμ–΄λ₯Ό μ§€μ›ν•˜κ²Œ λ˜λŠ” μˆœκ°„ μ“Έλͺ¨μ—†λŠ” ν˜Ήλ©μ–΄λ¦¬κ°€ 될 λΏμž…λ‹ˆλ‹€. μ‘°κΈˆμ΄μ§€λ§Œ νŠΈλž˜ν”½λ„ 더 λ°œμƒν•  것이고 μ„œλ²„ κ°œλ°œμžλ³΄λ‹¨ ν΄λΌμ΄μ–ΈνŠΈ κ°œλ°œμžκ°€ νž˜λ“€μ–΄μ§€λŠ” 상황이 λ°œμƒν•©λ‹ˆλ‹€. λ‹¨νŽΈμ μΈ 예둜 Github의 API도 μ‘λ‹΅μ˜ Bodyμ—” λ°μ΄ν„°λ§Œ λ‹΄μ•„μ„œ λ³΄λ‚΄μ£ΌλŠ” ν˜•μ‹μœΌλ‘œ 이루어져 μžˆμŠ΅λ‹ˆλ‹€.

Solution : OkHttp Custom InterceptorπŸ˜€

ν•˜μ§€λ§Œ μš°λ¦¬λŠ” 항상 개발의 초기 단계뢀터 κ΄€μ—¬ν•  수 μžˆμ§€ μ•Šκ³ , 이미 개발된 μ„œλ²„μ— λ§žμΆ°μ„œ μ•ˆλ“œλ‘œμ΄λ“œ μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜μ„ κ°œλ°œν•΄μ•Ό ν•  μˆ˜λ„ μžˆμŠ΅λ‹ˆλ‹€. 이럴 λ•Œ μš°λ¦¬λŠ” OkHttp의 Interceptorλ₯Ό μ΄μš©ν•΄μ„œ Retrofit을 μ΄μš©ν•œ μ„œλ²„ μš”μ²­κ³Ό 응닡을 μ€‘κ°„μ—μ„œ κ°€λ‘œμ±„ μ›ν•˜λŠ” 값을 λΌμ›Œμ„œ μš”μ²­ν•˜κ±°λ‚˜ 응닡을 μ›ν•˜λŠ” ν˜•νƒœλ‘œ 쑰금 λ³€ν˜•ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Retrofit은 λ‚΄λΆ€μ μœΌλ‘œ OkHttpλΌλŠ” Http 톡신 라이브러리λ₯Ό μ‚¬μš©ν•˜κ³  μžˆμŠ΅λ‹ˆλ‹€. λ‹€μŒ 그림은 OkHttpμ—μ„œ Interceptor의 κ°œλ…μ΄ 무엇인지 λ‹¨νŽΈμ μœΌλ‘œ μ„€λͺ…ν•΄μ€λ‹ˆλ‹€.

μœ—λΆ€λΆ„μ΄ 우리의 μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜, μ•„λž«λΆ€λΆ„μ΄ μ„œλ²„λΌκ³  λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. κ·Έλƒ₯ InterceptorλŠ” μ–΄ν”Œλ¦¬μΌ€μ΄μ…˜ λ‚΄μ—μ„œ OkHttp μ½”μ–΄ μ‚¬μ΄μ—μ„œ μš”μ²­/응닡을 κ°€λ‘œμ±„λŠ” 역할을 ν•˜κ³ , NetworkInterceptorλŠ” μ‹€μ œ ν†΅μ‹ μ—μ„œ μ„œλ²„μ™€ OkHttp μ½”μ–΄ μ‚¬μ΄μ—μ„œ μš”μ²­/응닡을 κ°€λ‘œμ±„λŠ” 역할을 ν•©λ‹ˆλ‹€.

결둠적으둜 Interceptorλ₯Ό μž₯μ°©ν•΄μ„œ μ–΄λ–»κ²Œ Retrofit 객체λ₯Ό μ–»μ–΄λƒˆλŠ”μ§€ μ½”λ“œλ₯Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

private val client = OkHttpClient.Builder()
.addNetworkInterceptor(commonNetworkInterceptor)
.build()

private val retrofit = Retrofit.Builder()
.baseUrl(BASE_URL)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()

Retrofit.Builderλ₯Ό μ΄μš©ν•˜μ—¬ Retrofit 객체λ₯Ό λ§Œλ“€ λ•Œ, κ·Έλƒ₯ λ§Œλ“  것이 μ•„λ‹ˆλΌ OkHttpClient 객체λ₯Ό .clinet() λ©”μ„œλ“œλ‘œ νƒ‘μž¬ν•΄μ„œ μƒμ„±ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€. 그리고 OkHttpClient κ°μ²΄λŠ” addNetworkInterceptor λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄ μ–΄λ–€ Interceptor 객체λ₯Ό μ „λ‹¬ν•΄μ„œ μƒμ„±ν•΄μ£Όμ—ˆμŠ΅λ‹ˆλ‹€.

commonNetworkInterceptor κ°μ²΄λŠ” λ‹€μŒκ³Ό 같이 μƒμ„±λ©λ‹ˆλ‹€.

private val commonNetworkInterceptor = object : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
.......
}
}

InterceptorλΌλŠ” μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•΄μ„œ interceptλΌλŠ” λ©”μ„œλ“œλ₯Ό μ˜€λ²„λΌμ΄λ”©ν•΄μ£ΌκΈ°λ§Œ ν•˜λ©΄ λ©λ‹ˆλ‹€. 이제 이 μ•ˆμ—μ„œ 무슨 일이 μΌμ–΄λ‚˜λŠ”μ§€ μ°¨κ·Όμ°¨κ·Ό μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

/**
* 1) Common Header with API Access Token
*/
val newRequest = chain.request().newBuilder()
.addHeader("token", SPUtil.accessToken).build()
  1. 곡톡 헀더 μž₯μ°©(선택) : μš°μ„ , μš°λ¦¬λŠ” μ‘λ‹΅μœΌλ‘œ 온 chain 객체λ₯Ό μ΄μš©ν•΄μ„œ 곡톡적인 헀더λ₯Ό 달아쀄 수 μžˆμŠ΅λ‹ˆλ‹€. μ΄λŠ” 응닡을 νŒŒμ‹±ν•˜λŠ” κ²ƒκ³ΌλŠ” κ΄€λ ¨μ—†μ§€λ§Œ, μ„œλ²„μ— 항상 곡톡적인 Access Token을 Header에 달아쀄 λ•Œ μœ μš©ν•©λ‹ˆλ‹€. 즉, Retrofit API μΈν„°νŽ˜μ΄μŠ€λ₯Ό μ •μ˜ν•  λ•Œ, @Headerμ–΄λ…Έν…Œμ΄μ…˜μ„ 달아쀄 ν•„μš”κ°€ μ—†μ–΄μ§‘λ‹ˆλ‹€.
/**
* 2) General Response from Server (Unwrapping data)
*/
val response = chain.proceed(newRequest)

2. μ„œλ²„λ‘œλΆ€ν„° 응닡받기 : chain의 proceed λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜λ©΄ μ„œλ²„λ‘œ 톡신을 ν•˜κ³  응닡을 λ°›μ•„μ˜¬ 수 있게 λ©λ‹ˆλ‹€. return κ°’μœΌλ‘œ 받은 response 객체가 λ°”λ‘œ κ·Έ 응닡을 μ˜λ―Έν•˜λŠ” κ°μ²΄μž…λ‹ˆλ‹€.

/**
* 3) Parse body to json
*/
val rawJson = response.body?.string() ?: "{}"

3. μ‘λ‹΅μ˜ bodyλ₯Ό JSON λ¬Έμžμ—΄λ‘œ λ§Œλ“€κΈ°: bodyλ₯Ό toString() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄μ„œ 일반 λ¬Έμžμ—΄λ‘œ λ³€ν™˜ν–ˆμŠ΅λ‹ˆλ‹€.

/**
* 4) Wrap body with gson
*/
val type = object : TypeToken<ResponseWrapper<*>>() {}.type
val res = try {
val r = gson.fromJson<ResponseWrapper<*>>(rawJson, type) ?: throw JsonSyntaxException("Parse Fail")

if(!r.success)
ResponseWrapper<Any>(-999, false, "Server Logic Fail : ${r.message}", null) //1
else
r

} catch (e: JsonSyntaxException) {
ResponseWrapper<Any>(-999, false, "json parsing fail : $e", null) //2
} catch (t: Throwable) {
ResponseWrapper<Any>(-999, false, "unknown error : $t", null) //3
}

4. JSON λ¬Έμžμ—΄μ„ ResponseWrapper ν˜•μ‹μ˜ 객체둜 Gson을 μ΄μš©ν•΄μ„œ λ³€ν™˜ν•˜κΈ° : Gson은 JSON λ¬Έμžμ—΄μ„ POJO(Plain-Old-Java-Object)λ‚˜ μ½”ν‹€λ¦° 데이터 클래슀둜 μ‰½κ²Œ λ³€ν™˜ν•  수 있게 λ„μ™€μ£ΌλŠ” λΌμ΄λΈŒλŸ¬λ¦¬μž…λ‹ˆλ‹€. fromJson<T>(String, Type) λ©”μ„œλ“œλ₯Ό μ΄μš©ν•˜λ©΄ μš°λ¦¬κ°€ κ°–κ³  μžˆλŠ” JSON λ¬Έμžμ—΄ 객체λ₯Ό ResponseWrapper 객체둜 λ°”κΏ€ 수 μžˆμŠ΅λ‹ˆλ‹€. 이 κ³Όμ •μ—μ„œ 또 3 κ°€μ§€μ˜ 문제 상황을 μ •μ˜ν•  수 μžˆμŠ΅λ‹ˆλ‹€. μ£Όμ„μœΌλ‘œ μ²˜λ¦¬ν•΄λ‘” 숫자λ₯Ό 보면,

1- JSON λ¬Έμžμ—΄μ΄ 우리의 ResponseWrapper 객체둜 νƒˆλ°”κΏˆμ€ μ„±κ³΅ν–ˆμ§€λ§Œ, success ν•„λ“œκ°€ falseλΌμ„œ μ„œλ²„λ‹¨μ—μ„œ 정상적인 μš”μ²­μ΄ μ‹€νŒ¨ν•œ κ²½μš°μž…λ‹ˆλ‹€.

2- μ•„μ˜ˆ ResponseWrapper둜 νƒˆλ°”κΏˆλ„ μ‹€νŒ¨ν•œ κ²½μš°μž…λ‹ˆλ‹€.

3- μ•Œμˆ˜μ—†λŠ” μ—λŸ¬κ°€ λ°œμƒν•œ κ²½μš°μž…λ‹ˆλ‹€.

κ³΅ν†΅μ μœΌλ‘œ μ—λŸ¬κ°€ λ°œμƒν•˜λ“  말든 ResponseWrapperλΌλŠ” 객체둜 μ½”ν‹€λ¦° 문법을 μ΄μš©ν•΄μ„œ λ§Œλ“€μ—ˆμŠ΅λ‹ˆλ‹€. μ΄λ ‡κ²Œ λ§Œλ“  μ΄μœ λŠ” λ‹€μŒ λ‹¨κ³„μ—μ„œ λ³Ό 수 μžˆμŠ΅λ‹ˆλ‹€. resλΌλŠ” 객체에 ResponseWrapper 객체λ₯Ό κ°–κ³  있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

/**
* 5) get data json from data
*/
val dataJson = gson.toJson(res.data)

5. μ›ν•˜λŠ” 데이터λ₯Ό JSON λ¬Έμžμ—΄λ‘œ λ³€ν™˜ : 이제 res κ°μ²΄μ—μ„œ 데이터 객체λ₯Ό λ‹€μ‹œ JSON λ¬Έμžμ—΄λ‘œ Gson의 toJson λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄μ„œ λ§Œλ“­λ‹ˆλ‹€.

/**
* 6) return unwrapped response with body
*/
return response.newBuilder()
.message(res.message)
.body(dataJson.toResponseBody())
.build()

6. κΈ°μ‘΄ 응닡에 Message와 Body만 κ΅μ²΄ν•΄μ„œ 응닡 전달 : κΈ°μ‘΄ κ°–κ³  있던 응닡인 response 객체에 newBuilder() λ©”μ„œλ“œλ₯Ό μ΄μš©ν•΄μ„œ 응닡을 λ³΅μ‚¬ν•˜κ³ , messageμ—” μ€€λΉ„ν•΄λ‘” message(성곡일 땐 μ„œλ²„μ—μ„œμ˜ message κ·ΈλŒ€λ‘œ, μ‹€νŒ¨μΌ 땐 κ³Όμ • 4의 μ—λŸ¬ 메세지)λ₯Ό, dataμ—” 우리의 데이터λ₯Ό μ˜λ―Έν•˜λŠ” JSON λ¬Έμžμ—΄μ„ RequestBody 객체둜 λ³€ν™˜ν•˜μ—¬ λ„£μ–΄μ€λ‹ˆλ‹€.

λ‹€μŒμ€ Interceptor 전체 μ½”λ“œμž…λ‹ˆλ‹€.

ResultπŸ’

우리의 μ½”λ“œκ°€ μ–΄λ–»κ²Œ λ³€ν™”λ˜μ—ˆλŠ”μ§€ μ‚΄νŽ΄λ³΄κ² μŠ΅λ‹ˆλ‹€.

μš°μ„  Retrofit의 API Interface의 λ©”μ„œλ“œμž…λ‹ˆλ‹€. ResponseWrapperκ°€ μ€‘κ°„μ—μ„œ λͺ¨μŠ΅μ„ κ°μΆ”μ—ˆμŠ΅λ‹ˆλ‹€.

without ResponseWrapper!

그리고 μ•„λž˜μ™€ 같이 Network μš”μ²­ μ½”λ“œλ₯Ό 지 수 있게 λ˜μ—ˆμŠ΅λ‹ˆλ‹€.

without ResponseWrapper logic check!

μ—¬μ „νžˆ λ„€νŠΈμ›Œν¬ 톡신 μ½”λ“œκ°€ κΉ”λ”ν•˜μ§„ μ•Šμ§€λ§Œ κ·ΈλŸ­μ €λŸ­ μ‚¬μš©ν•΄μ§ˆλ§Œν•΄μ‘ŒμŠ΅λ‹ˆλ‹€.

μš°λ¦¬κ°€ μ›ν•˜λŠ” 데이터 : [Funding(nickname=null, fundingIdx=167, userIdx=113, storeIdx=30, fundingMoney=4500, rewardPercent=0, fundingTime=2020-01-03T15:22:02.000Z, rewardMoney=9000)]

λ„€νŠΈμ›Œν¬ μš”μ²­λ„ κΉ”λ”ν•˜κ²Œ μ„±κ³΅ν–ˆμŠ΅λ‹ˆλ‹€.

그리고 μœ„μ™€ 같이 Interceptorλ₯Ό μž₯μ°©ν•˜λŠ” 경우,

{
"status": 200,
"success": true,
"message": "μ‚¬μš© κ°€λŠ₯ν•œ λ‹‰λ„€μž„μž…λ‹ˆλ‹€."
}

μœ„μ™€ 같이 dataκ°€ μ—†λŠ” 응닡도 μžλ™μœΌλ‘œ Codeλ₯Ό 기반으둜 μ„±κ³΅μœΌλ‘œ μ²˜λ¦¬ν•΄μ€λ‹ˆλ‹€.

Bonus : Coroutine❀️

졜근 ν”„λ‘œμ νŠΈμ—μ„œ μ‚¬μš©ν•œ Coroutine은 μœ„μ™€ 같은 μ½”λ“œλ₯Ό ν•„μš” μ—†κ²Œ ν•΄μ€λ‹ˆλ‹€. Retrofit 2.6.0λΆ€ν„° suspend ν•¨μˆ˜λ₯Ό μ΄μš©ν•œ 코루틴 응닡 지원이 μƒκ²ΌλŠ”λ°, λ‹€μŒκ³Ό 같은 μ½”λ“œλ₯Ό μž‘μ„±ν•  수 있게 ν•©λ‹ˆλ‹€.

@GET("mypage/fundlist")
suspend fun getMyFundingHistories() : List<Funding>

μœ„μ™€ 같이 Call도 없이 suspend ν•¨μˆ˜λ‘œ API Interface λ©”μ„œλ“œλ₯Ό μ •μ˜ν•˜κ³ ,

lifecycleScope.launchWhenCreated {
kotlin.runCatching {
NetworkClient.fundingService.listFundingHistories()
}.onSuccess {
Timber.e("데이터 : $it")
}.onFailure {
Timber.e(it)
}
}

Structured Concurrency의 CoroutineScopeλ₯Ό μ΄μš©ν•΄ μ›ν•˜λŠ” λ„€νŠΈμ›Œν¬ 톡신을 κ°„κ²°ν•˜κ²Œ κ°€λŠ₯ν•˜κ²Œ ν•˜κ³ , μ•‘ν‹°λΉ„ν‹°κ°€ 파괴될 λ•Œ μžλ™μœΌλ‘œ 톡신이 μ·¨μ†Œλ˜λ„λ‘ λ§Œλ“€μ–΄μ€λ‹ˆλ‹€.

Conclusionβœ”οΈ

μ—¬κΈ°κΉŒμ§€ ν•΄μ„œ OkHttp의 Interceptorλ₯Ό μ΄μš©ν•΄ μ„œλ²„μ˜ JSON 응닡 쀑, μš°λ¦¬κ°€ μ›ν•˜λŠ” 데이터 λΆ€λΆ„λ§Œ λ½‘μ•„μ„œ λ³„λ„μ˜ νŒŒμ‹± 없이 λ°”λ‘œ μ‚¬μš©ν•  수 μžˆλŠ” μ½”λ“œλ₯Ό 지 수 μžˆλŠ” 방법에 λŒ€ν•΄μ„œ μ•Œμ•„λ³΄μ•„μŠ΅λ‹ˆλ‹€.

Retrofitκ³Ό Gson을 κ³΅λΆ€ν•˜λ©΄ κ³΅λΆ€ν•˜λŠ” 만큼 Enum을 μš”μ²­/응닡에 μ‚¬μš©ν•˜κ³  λ„€νŠΈμ›Œν¬ ν†΅μ‹ μ˜ μ—¬λŸ¬ 상황에 λŒ€ν•΄ 깊이 μ΄ν•΄ν•˜κ³  λ‹€λ£¨λŠ” 법에 λŠ₯μˆ™ν•΄μ§€κ²Œ λ©λ‹ˆλ‹€. ν”Όλ“œλ°±μ€ μ–Έμ œλ‚˜ ν™˜μ˜μž…λ‹ˆλ‹€.

--

--