기존에 만들어두었던 토이 프로젝트의 공공 API가 더 이상 서비스되지 않아 API를 교체했습니다.
기존에 사용했던 코로나 관련 API의 경우 JSON으로 응답을 내려줬지만, 교체하게 된 API의 경우 XML로만 내려주더라구요.
SimpleXmlConverter 는 deprecated 되었고, JAXB(Jakarta XML Binding) converter 는 안드로이드를 지원하지 않아서~Tikxml를 사용했습니다.
어노테이션이 생소해서 그렇지 사용하기 편리한 라이브러리입니다. 기존에 retrofit으로 JSON 파싱하는 것과 거의 동일합니다. 그냥 GSON 대신 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'
object RetrofitClient {
fun getXMLInstance() : Retrofit{
val parser = TikXml.Builder().exceptionOnUnreadXml(false).build()
return Retrofit.Builder()
.baseUrl(Constants.BASE_URL)
.addConverterFactory(TikXmlConverterFactory.create(parser))
.build()
}
}
TikXml.Builder().exceptionOnUnreadXml(false).build() 는 원하지 않는 데이터는 제외하기 위해서입니다. 내려받은 데이터를 모두 사용하신다면 추가하지 않아도 됩니다.여기서부터 조금 생소하게 느껴질 수 있습니다. JSON 넣으면 data class 바로 뽑아주는 친절한 코틀린에 익숙해져버린 저는 버벅였지만 여러분은 절대 어렵게 느끼지 않을 거예요.
<response>
<header>
<resultCode>00</resultCode>
<resultMsg>NORMAL SERVICE.</resultMsg>
</header>
<body>
<items>
<item>
<addr>부산광역시 해운대구 좌동순환로 173 502호 (좌동, 영풍프라자)</addr>
<mgtStaDd>20220215</mgtStaDd>
<pcrPsblYn>N</pcrPsblYn>
<ratPsblYn>Y</ratPsblYn>
<recuClCd>31</recuClCd>
<sgguCdNm>부산해운대구</sgguCdNm>
<sidoCdNm>부산</sidoCdNm>
<XPos>995224</XPos>
<XPosWgs84>129.1752749</XPosWgs84>
<YPos>471795</YPos>
<YPosWgs84>35.1781714</YPosWgs84>
<yadmNm>정내과의원</yadmNm>
<ykihoEnc>JDQ4MTAxMiM1MSMkMiMkMCMkMDAkMzgxMTkxIzUxIyQxIyQxIyQ4MiQyNjEwMDIjNzEjJDEjJDgjJDgz</ykihoEnc>
</item>
</items>
<numOfRows>1</numOfRows>
<pageNo>1</pageNo>
<totalCount>4469</totalCount>
</body>
</response>
위와 같은 형식으로 내려올 경우 다음과 같이 POJO를 작성해줍니다.
Item의 경우 사용할 데이터만 작성했습니다.
@Xml(name = "response")
data class Hospital(
@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
data class Item(
@PropertyElement(name = "addr")
var addr: String,
@PropertyElement(name = "sidoCdNm")
var sidoCdNm: String,
@PropertyElement(name="yadmNm")
var yadmNm: String,
)
자세한 사용법은 아래 tikxml 공식 문서에 설명되어 있습니다.
https://github.com/Tickaroo/tikxml/blob/master/docs/AnnotatingModelClasses.md
간단히 살펴보면
@PropertyElement는 nest child element가 없는 경우
@Element는 nest child element를 가진 경우
그러니까, 딸린 자식이 있으면
@Element
싱글이면@PropertyElement
(name="yadmNm") 공식 문서에 나와있듯 optional입니다.
이게 전부입니다. 나머지는 retrofit 쓰시던 그대로 하시면 됩니다.
Could not locate ResponseBody converter...
저를 삽질하게 한 오류입니다. 이틀을 날렸어요 만약 이 오류를 보신다면 아래 사항들을 확인해보세요.
data class 즉, POJO를 제대로 작성했는지
XML can only be deserialized to a concrete type and not a list of types like JSON. In this case, the body type should represent the tag and its children and not List.
마지막으로, 통신이 제대로 가고 있는지,,,

- 제가 이랬습니다. 평소에 Interceptor 꼭꼭 달아주는 습관을 들이세요. 왜냐면 정신건강에 좋거든요. 로그만 제대로 찍어봤어도 제가 잘못된 인증키를 사용하고 있다는 걸 금방 알 수 있었을 겁니다.
포털에서 제공되는 Encoding/Decoding 된 인증키를 적용하면서 구동되는 키를 사용하시기 바랍니다.
이런 말은 절대 넘겨짚지 마세요🥺
요즘은 대부분 JSON을 사용하지만 간혹 XML을 파싱해야할 일이 생길수도(?) 있으니까요. 혹시라도 생겨서 들어오셨다면 이럇시아리마세! tikxml 사용해보시는 것도 좋을 거 같습니다. 특히 공공기관 API는 XML을 내려주는 경우가 종종 있습니다.
참고자료
https://medium.com/@sameerzinzuwadia/android-kotlin-xml-parsing-with-retrofit-6879401d7901
https://developer-eungb.tistory.com/24