Retrofit으로 서버에 필요한 데이터를 요청한다. 데이터를 응답받을 때 응답 받은 데이터 중 필요한 데이터만 사용하고 싶은 경우가 있다. 예로 살펴보자.
{
"coord": {
"lon": 129.3343,
"lat": 36.0982
},
"weather": [
{
"id": 800,
"main": "Clear",
"description": "clear sky",
"icon": "01d"
}
],
"base": "stations",
"main": {
"temp": 275.83,
"feels_like": 271.91,
"temp_min": 275.83,
"temp_max": 275.83,
"pressure": 1030,
"humidity": 33,
"sea_level": 1030,
"grnd_level": 1026
},
"visibility": 10000,
"wind": {
"speed": 4.41,
"deg": 297,
"gust": 6.52
},
"clouds": {
"all": 0
},
"dt": 1669948994,
"sys": {
"country": "KR",
"sunrise": 1669932936,
"sunset": 1669968513
},
"timezone": 32400,
"id": 1832015,
"name": "Heunghae",
"cod": 200
}
위의 응답은 openweathermap api에서 응답받은 데이터이다. 여기서 우리가 사용해야할 데이터는 weather - id, main, icon
과 main - temp
이다. Application 단에서 이 부분만 받도록 가공해야한다.
데이터를 가공하는 작업을 Interceptor를 통해서 할 수 있다. Application 단으로 가기 전 데이터를 가로채서 가공한 후 필요한 데이터만 제공해주는 작업을 진행할 것이다.
val request = chain.request()
val response = chain.proceed(request)
val jsonString = response.body?.string() ?: ""
val json =Json{ignoreUnknownKeys = true}
val result = json.decodeFromString<WeatherContainerResponse>(jsonString)
ignoreUnknownKeys
를 true로 설정하여 사용하지 않는 속성은 정의할 필요가 없도록 설정해준다.
kotlinx serialization은 decodeFromString
메서드를 활용하여 객체로 변환할 수 있지만 gson이라면 TypeToken
을 통해 객체화 작업을 진행해야한다.
response.newBuilder()
.message(response.message)
.body(Json.encodeToString(
WeatherResponse(
weather.id,
weather.type,
weather.icon,
main.temperature
)
).toResponseBody())
.build()
weather의 속성과 main의 속성을 결합해야하는 구조라서 WeatherResponse 라는 데이터 클래스로 만들어 body에 넣어주었다.
전체 코드
@Provides
@Singleton
@Named("Filtering")
fun provideFilteringInterceptor(): Interceptor {
return Interceptor { chain->
val request = chain.request()
val response = chain.proceed(request)
val jsonString = response.body?.string() ?: ""
val json =Json{ignoreUnknownKeys = true}
val result = json.decodeFromString<WeatherContainerResponse>(jsonString)
val weather = result.weathers.first()
val main = result.main
response.newBuilder()
.message(response.message)
.body(Json.encodeToString(
WeatherResponse(
weather.id,
weather.type,
weather.icon,
main.temperature
)
).toResponseBody())
.build()
}
}
@Provides
@Singleton
@Named("Weather")
fun provideWeatherOkHttpClient(
loggingInterceptor: HttpLoggingInterceptor,
@Named("Weather") weatherHeaderInterceptor: Interceptor,
@Named("Filtering") filteringInterceptor: Interceptor
): OkHttpClient {
return OkHttpClient().newBuilder()
.addInterceptor(loggingInterceptor)
.addInterceptor(weatherHeaderInterceptor)
.addInterceptor(filteringInterceptor)
.addNetworkInterceptor(StethoInterceptor())
.build()
}
addInterceptor
로 filteringInterceptor를 추가해준다. Hilt를 사용했고 여러 Interceptor가 있기 때문에 @Named
애노테이션으로 이름을 지정해주었다.
@Serializable
data class WeatherResponse(
val id: Long,
val type: String,
val icon: String,
val temperature: Double,
)
@Serializable
data class WeatherTypeResponse(
@SerialName("id") val id: Long,
@SerialName("main") val type: String,
@SerialName("icon") val icon: String
)
@Serializable
data class TemperatureResponse(
@SerialName("temp") val temperature: Double
)
@Serializable
data class WeatherContainerResponse(
@SerialName("weather") val weathers: List<WeatherTypeResponse>,
@SerialName("main") val main: TemperatureResponse
)
@GET("/data/2.5/weather")
suspend fun getWeatherData(
@Query("lat") latitude: Double,
@Query("lon") longitude: Double,
): WeatherResponse
Application 단에서 반환되는 값은 필요한 데이터만 가공된 WeatherResponse
이다. DataSource나 Repository에서 데이터 가공을 할 필요 없어진 것을 확인할 수 있다.
참고자료