문제 배경
대부분 어플리케이션을 만들 때 서버와 API통신을 하며 json을 주고받고 있습니다. 클라이언트와 서버 사이의 API 데이터에는 서버 통신이 잘 되었는지, 응답이 잘 갔는지, 잘못된 형태로 보내진 건 아닌지에 대해 확인하기 위해 resultCode, message, data와 같은 형식으로 제공되며 data는 정말 다양한 형식으로 올 수 있습니다.
예전의 저는 api 데이터를 받기 위해 항상 아래와 같은 data class를 매번 생성해주곤 했습니다.
data class ServerResponse {
val resultCode: String,
val message: String,
val data: ServerData
}
data class ServerData {
val id: Long,
val dataName: String,
val dataType: String
}
이런 코드를 수정하기 위해 저는 Generic을 사용하여 매번 response를 생성하는 부분을 해결하였지만
Service 내에서 내부 Generic을 선언해주기 위해 <>가 늘어나는 것을 볼 수 있었습니다.
data class ServerResponse<T> {
val resultCode: String,
val message: String,
val data: T
}
interface NetworkService {
@GET("/test")
fun getData(): Call<ServerResponse<ServerData>>
}
"OkHttp에서 Interceptor는 Call들을 모니터링하고 다시 쓰고 재시도할 수 있는 강력한 메커니즘입니다. 여기 발신 요청과 수신 응답을 기록하는 간단한 인터셉트가 있습니다." 라고 설명해주고 있으며 아래 그림과 같은 형태로 동작하고 있습니다. 저는 <>가 늘어나는 과정을 최소화하기 위해 Interceptor를 설계해봤습니다.
Interceptor 설계 (해당 코드들은 아래 Github 링크에 있습니다)
1. retrofit builder에 client를 추가해준다.
object RetrofitClient {
private const val baseUrl = "http://10.0.2.2:3001"
private val client: OkHttpClient = OkHttpClient.Builder()
.addNetworkInterceptor(CommonNetworkInterceptor())
.build()
private val retrofit = Retrofit.Builder()
.baseUrl(baseUrl)
.client(client)
.addConverterFactory(GsonConverterFactory.create())
.build()
// ... 추가 내용 생략
}
2. CommonNetworkInterceptor 생성
class CommonNetworkInterceptor : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
// ...
}
}
3. AccessToken를 헤더에 붙여주기
var request = chain.request()
/**
* 1) Common Header with API Access Token
* API Access Token을 헤더에 추가하는 역할을 합니다
*/
val accessToken = MyApplication.spUtil.getString(ACCESS_TOKEN, null)
accessToken?.let {
request = request.newBuilder()
.addHeader(headerTokenKey, accessToken).build()
}
4. 해당 request를 통해 response 받기
/**
* 2) General Response from Server (Unwrapping data)
*/
val response = chain.proceed(request)
5. json의 형태가 resultCode, message, data일 때와 아닐 때를 나눠서 구분해줍니다
/**
* 3) Parse body to json,
* if body is in the form of [ResponseWrapper], save the message and result separately
*/
val responseJson = response.extractResponseJson()
val message = if (responseJson.has(messageKey)) responseJson[messageKey].toString() else ""
val dataPayload = if (responseJson.has(dataKey)) responseJson[dataKey] else responseJson
} // intercept func end
private fun Response.extractResponseJson(): JSONObject {
val jsonString: String = this.body?.string() ?: "{}"
return try {
JSONObject(jsonString)
} catch(exception: Exception) {
Log.d("UnboxingInterceptor", "서버 응답이 json이 아님: $jsonString")
throw java.lang.Exception()
}
}
6. 해당 json을 다시 조합해서 return 해줍니다
return response.newBuilder()
.message(message)
.body(dataPayload.toString().toResponseBody())
.build()
<예시 코드>
<참고자료>
'Android' 카테고리의 다른 글
[Android] BaseFragment 만들기 (2) | 2023.02.02 |
---|---|
[Android] Gradle을 KTS로 마이그레이션 하기 (1) | 2023.02.02 |
[Android] 에뮬레이터에서 localhost 접속하기 (0) | 2022.12.22 |
[Coil] 이미지 로딩 속도 체크하기 (0) | 2022.12.04 |
[Glide] 이미지 로딩 속도 체크하기 (0) | 2022.12.04 |
댓글