Retrofit2 사용할 때 Json이 아닌 xml일 경우!
공공 데이터 API를 사용하다보면 모든 파일이 Json을 지원하진 않기 때문에
불가피하게 Xml로 변환해야하는 경우들이 있는데요,,
저도 Json으로만 하다가 이번에 처음으로 Xml로 변환하는 것을 시도해봤습니다,,!
함께 봐보겠습니다!
종속성 추가!
//xml parser
implementation ("com.tickaroo.tikxml:annotation:0.8.13")
implementation ("com.tickaroo.tikxml:core:0.8.13")
implementation ("com.tickaroo.tikxml:retrofit-converter:0.8.13")
kapt ("com.tickaroo.tikxml:processor:0.8.13")
implementation ("com.squareup.retrofit2:adapter-rxjava2:2.11.0")
Converter
object RetrofitSunsetInstance {
const val LOCAL_KEY = "내 키 값 ㅎㅎ"
private fun httpLoggingInterceptor(): Interceptor {
return HttpLoggingInterceptor{message -> Log.e("MyOkHttpSunset : ", message + "")}
.setLevel(HttpLoggingInterceptor.Level.BODY)
}
val client: OkHttpClient by lazy {
OkHttpClient.Builder()
.addInterceptor(httpLoggingInterceptor())
.build()
}
private val getInstance: Retrofit by lazy {
val parser = TikXml.Builder().exceptionOnUnreadXml(false).build()
Retrofit.Builder()
.baseUrl("http://apis.data.go.kr/B090041/openapi/service/RiseSetInfoService/")
.addCallAdapterFactory(RxJava2CallAdapterFactory.create())
.addConverterFactory(TikXmlConverterFactory.create(parser))
.client(client)
.build()
}
val retrofitService: RetrofitSunService by lazy {
getInstance.create(RetrofitSunService::class.java)
}
}
위의 코드에서
이 부분이 과연 무얼 의미하는 지 궁금하실 거 같아서 찾아봤습니다!
이는 원하지 않는 데이터는 제외해주기 위해 사용합니다
만약 받아오는 모든 데이터를 받아온다면 사용하실 필요가 없습니다!!
DataClass
<response>
<header>
<resultCode>00</resultCode>
<resultMsg>NORMAL SERVICE.</resultMsg>
</header>
<body>
<items>
<item>
<aste>185531</aste>
<astm>060706</astm>
<civile>175208</civile>
<civilm>071028</civilm>
<latitude>3613</latitude>
<location>추풍령</location>
<locdate>20150101</locdate>
<longitude>12800</longitude>
<moonrise>142713</moonrise>
<moonset>033424</moonset>
<moontransit>212833</moontransit>
<naute>182415</naute>
<nautm>063822</nautm>
<sunrise>073903</sunrise>
<sunset>172334</sunset>
<suntransit>123115</suntransit>
</item>
</items>
<numOfRows>10</numOfRows>
<pageNo>1</pageNo>
<totalCount>1</totalCount>
</body>
</response>
위의 형식으로 가져올 경우 다음과 같이 POJO를 작성했습니다!
@Xml(name = "response")
data class SunsetApi(
@Element(name = "body")
val body: Body,
@Element(name = "header")
val header: Header
)
@Xml(name = "header")
data class Header(
@PropertyElement(name = "resultCode")
val resultCode: Int,
@PropertyElement(name = "resultMsg")
val resultMsg: String
)
@Xml(name = "body")
data class Body(
@Element(name = "items")
val items: Items,
@PropertyElement(name = "numOfRows")
val numOfRows: Int,
@PropertyElement(name = "pageNo")
val pageNo:Int,
@PropertyElement(name = "totalCount")
val totalCount:Int
)
@Xml(name = "items")
data class Items(
@Element(name = "item")
val item: List<Item>
)
@Xml(name = "Item")
data class Item(
@PropertyElement(name = "locdate")
val locdate:String,
@PropertyElement(name = "location")
val location:String,
@PropertyElement(name = "longitudeNum")
val longitudeNum:String,
@PropertyElement(name = "latitudeNum")
val latitudeNum:String,
@PropertyElement(name = "sunrise")
val sunrise:Int,
@PropertyElement(name = "suntransit")
val suntransit:Int,
@PropertyElement(name = "sunset")
val sunset:Int
)
아 여기서 잠깐!
@Element와 @PropertyElement의 차이는 무엇일까?
1, @Element 는 딸린 자식이 있을 경우 정의하는데 위의 코드를 예시로 보자면
@Element(name = "body")의 경우 딸린 자식이 있기 때문에 @Element를 사용하고
2, @PropertyElement 의 경우 딸린 자식이 없을 경우 사용한다
예를 들어 위의 코드의 @PropertyElement(name = "sunset")은 딸린 자식 없이 Int로 받기 때문에
@PropertyElement를 사용한다!
저는 아까 Converter에서 아래의 코드를 작성했기 때문에
val parser = TikXml.Builder().exceptionOnUnreadXml(false).build()
필요한 정보만 받아오는 것으로 만들어 주었습니다
만약 위의 코드를 Converter에서 사용하지 않고
마음대로 받아오는 데이터를 줄인다면 에러가 나게 됩니다!
데이터를 요청하기 위해 필요한 Query를 interface에 정의
일출과 일몰 값을 알기 위해선 해당 지역의 지명과 위경도 값을 알아야 하기 때문에
추가해줬습니다!
@GET("getLCRiseSetInfo")
fun getSunset(
@Query("longitude") longitude:Int,
@Query("latitude") latitude:Int,
@Query("locdate") locdate: Int,
@Query("dnYn") dnYn:String,
@Query("ServiceKey") ServiceKey: String
): Call<SunsetApi>
데이터 받아오기
private fun getSunSetDataByRetrofit(){
val service = RetrofitSunsetInstance.retrofitService
val dateFormat = SimpleDateFormat("yyyyMMdd")
service.getSunset(12800, 3613, dateFormat,"N", RetrofitSunsetInstance.LOCAL_KEY)
.enqueue(object : retrofit2.Callback<SunsetApi>{
override fun onResponse(p0: Call<SunsetApi>, p1: Response<SunsetApi>) {
val result = p1.body()?.body
Log.d("test1234", "${result}")
}
override fun onFailure(p0: Call<SunsetApi>, p1: Throwable) {
Log.e("test1234", "${p1}")
}
})
}
현재는 연습중이라 MainActivity에서 이렇게 받아왔다!
근데!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
이런,,,에러가 떴다,,,ㅋㅋ
이것저것 찾아보니 위의 정의한 SunsetApi의 typeAdapter가 없다고 하는데,,,
이게 무슨 말이지 싶었다,,
이유를 알아냈다,,,
아까 데이터가 출력되지 않아 이것저것 손 보던 중
SunsetApi를 잘못 건들였는데 그게 문제였다,,
@Xml(name = "response")
data class SunsetApi(
@Element(name = "body")
val body: Body?,
@Element(name = "header")
val header: Header?
)
@Xml(name = "header")
data class Header(
@PropertyElement(name = "resultCode")
val resultCode: Int?,
@PropertyElement(name = "resultMsg")
val resultMsg: String?
)
현재 이 부분인데 내가 실수로
상단의 @Xml(name = "response")를 지워버린 것이었다,,,
암튼 다시 적어주니 잘 해결이 되었다,,ㅎㅎㅎ
근데..?
ㅇㅣ걸 해결하니 또 다른 문제가 기다리고 있었다,,
그건 바로,,,
이거였다,,
이 에러는 문자열을 숫자로 변환하려고 할 때 형식이 잘못돼서 발생하게 되었다고한다,,
그래서 뭐가 문제일까,,, 했는데
SunsetApi에서 받아오는 값을 String으로 바꿔주면 될 거 같아서 바꿔주었다!
@Xml(name = "Item")
data class Item(
@PropertyElement(name = "locdate")
val locdate: String?,
@PropertyElement(name = "location")
val location: String?,
@PropertyElement(name = "longitudeNum")
val longitudeNum: String?,
@PropertyElement(name = "latitudeNum")
val latitudeNum: String?,
@PropertyElement(name = "sunrise")
val sunrise: String?,
@PropertyElement(name = "suntransit")
val suntransit: String?,
@PropertyElement(name = "sunset")
val sunset: String?
)
이렇게 해주니 출력이 잘 되었다!
항상 Gson으로만 사용하다가 처음으로 Xml로 가져오는 방법을 사용해봤는데
어렵다,,
그래도 해결을 할 수 있어서 매우 뿌듯하다 ㅎㅎ