들어가기 전
현재 'ITda' 라는 이주 노동자들을 위한 서비스를 개발하고 있다. 나는 현재 뉴스페이지를 개발하고 있고 이 뉴스는 문화체육관광부의 정책뉴스 공공 API를 활용하기로 했다. 하지만 해당 API는 Json이 아닌 XML 형태로만 데이터를 보내주기 때문에 기존에 사용하던 JSON 파싱이 아닌 XML 데이터를 Retrofit에서 파싱해야했다. 따라서 이번 포스팅에서는 XML 데이터를 파싱해 전달받는 방법을 소개하고자 한다.
모든 예시는 아래 API와 함께한다.
https://www.data.go.kr/data/15095335/openapi.do
기존 데이터 전달 방식과의 비교
Retrofit 서버통신으로 데이터를 전달받을 때 많이 접하는 데이터 타입은 Json이다.
따라서 Android에서 data class 파일로 전환을 쉽게 할 수 있다.
{
"status": 200,
"message": "일일문답 조회에 성공했습니다.",
"data": {
"qna_id": 10,
"index": 2,
"section": "학창시절",
"topic": "topic",
"opponent_question": "~~?",
"my_question": "~?",
"opponent_answer": null,
"my_answer": null,
"is_opponent_answer": false,
"is_my_answer": false,
"opponent_username": "p1",
"my_username": "p2"
}
}
이렇게 Json Response를 알려주면 쉽게 아래 data class로 전환할 수 있다. (여러 converter 사이트도 이용할 수도 있다.)
@Serializable
data class ListResponseDto(
@SerialName("status")
val status: Int,
@SerialName("message")
val message: String,
@SerialName("data")
val data: List<ListData>
) {
@Serializable
data class ListData(
@SerialName("index")
val index: Int,
@SerialName("qna_id")
val qnaId: Long,
@SerialName("topic")
val topic: String
)
}
하지만 XML 데이터의 Response는 JSON 과 형식이 다르다.
<response>
<script/>
<header>
<resultCode>0</resultCode>
<resultMsg>NORMAL_SERVICE</resultMsg>
</header>
<body>
<NewsItem>
<NewsItemId>148896378</NewsItemId>
<ContentsStatus>U</ContentsStatus>
<ModifyId>11</ModifyId>
<ModifyDate>12/06/2021 16:36:06</ModifyDate>
<ApproveDate>12/03/2021 16:41:00</ApproveDate>
<ApproverName/>
<EmbargoDate/>
<GroupingCode>policy</GroupingCode>
<Title>
<![CDATA[ 정부 “차량·산업·농업용 요소·요소수 6개월분 이상 확보” ]]>
</Title>
<SubTitle1>
<![CDATA[ 주말에도 요소수 생산…일선 주유소 QR코드 도입 ]]>
</SubTitle1>
<SubTitle2/>
<SubTitle3/>
<ContentsType>H</ContentsType>
<DataContents>
...
</DataContents>
<MinisterCode>기획재정부</MinisterCode>
<OriginalUrl>
<![CDATA[ https://www.korea.kr/news/policyNewsView.do?newsId=148896378&call_from=openData ]]>
</OriginalUrl>
<ThumbnailUrl>
<![CDATA[ https://www.korea.kr/newsWeb/resources/attaches/2021.12/03/d9e18621c2644d4e26479df72d79262f.jpg ]]>
</ThumbnailUrl>
<OriginalimgUrl>
<![CDATA[ https://www.korea.kr/newsWeb/resources/attaches/2021.12/03/d38237aada75b98d52f36d9340e655ce.jpg ]]>
</OriginalimgUrl>
</NewsItem>
</body>
</response>
Response가 이렇게 오기 때문에 XML -> Dto를 만드는 과정이 기존의 방식과는 다르다.
Tikxml 활용한 XML 파싱
이전에는 xml 데이터를 파싱할 때 SimpleXmlConverter 를 사용했지만 현재는 deprecated 되었다.
따라서 Tikxml 을 이용해서 XML 데이터를 받고자 한다.
1. build gradle에 종속성 추가
//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'
2. XML -> Data class 작성하기
이 과정은 매우 중요하다. rseponseDto를 제대로 작성하지 않으면 데이터 전달이 제대로 이루어지지 않기 때문이다.
위 API 에서 XML response를 간단히 요약하면 다음과 같다.
response 태그 자식 -> script , header , body
header 태그의 자식 -> resultCode, resultMsg
body 태그의 자식 -> NewsItem의 list
NewsItem 태그의 자식 -> newsItemId, contentStatus .. 중략
자식 여부에 따라서 자식이 있다면 @Element, 없다면 @PropertyElement 어노테이션을 붙이면 된다.
import com.tickaroo.tikxml.annotation.Element
import com.tickaroo.tikxml.annotation.PropertyElement
import com.tickaroo.tikxml.annotation.Xml
@Xml(name = "response")
data class NewsResponse(
@PropertyElement (name="script")
val script: String?,
@Element (name="header")
val header: NewsHeader,
@Element (name="body")
val body: NewsBody
)
@Xml(name = "header")
data class NewsHeader(
@PropertyElement (name="resultCode")
val resultCode: String,
@PropertyElement (name="resultMsg")
val resultMsg: String
)
@Xml(name = "body")
data class NewsBody(
@Element(name="NewsItem")
val newsItems: List<NewsItem>
)
@Xml(name = "NewsItem")
data class NewsItem(
@PropertyElement(name="NewsItemId")
val newsItemId: String?,
@PropertyElement(name="ContentsStatus")
val contentsStatus: String?,
// 중략 . . .
)
작성한 Dto는 이렇다. @ 어노테이션 뒤에 name은 실제 response와 동일해야한다.
이 이름이 동일하지 않으면 제대로 파싱이 이루어지지 않는다.
또한 데이터가 선택적으로 들어올 수 있기 때문에 나는 NewsItem의 값들을 다 Nullable하게 처리했다.
이렇게 Dto를 작성했으면 거의 다 된 것이다.
Retrofit 인스턴스에 XML Converter 추가
Hilt로 DI를 적용했기 때문에 Network Module에서 Retrofit의 인스턴스를 생성하고있다.
이 때 이 Retrofit 인스턴스에 XML 데이터를 받을 수 있도록 Converter를 추가해줘야 한다.
@Provides
@Singleton
@Named("xmlConverter")
fun provideXmlConverterFactory(): Converter.Factory {
return TikXmlConverterFactory.create()
}
@Provides
@Singleton
@Named("xmlRetrofit")
fun provideNewsRetrofit(client: OkHttpClient, @Named("xmlConverter")xmlConverter: Converter.Factory): Retrofit =
Retrofit.Builder()
.baseUrl(BuildConfig.NEWS_BASE_URL) // XML 데이터 BASE_URL
.client(client)
.addConverterFactory(xmlConverter)
.build()
// XML 데이터를 통신하기 위한 Retrofit 인스턴스를 제공 메서드
provideXmlConverterFactory() 함수로 부터 xmlConverter를 받아 온 뒤, Retrofit addConverterFactory 메서드에 해당 매개변수를 넣어주면 된다.
이 함수에서 @Named 어노테이션을 사용한 이유는 이번 프로젝트에서 Json의 데이터를 주는 또 다른 base_url을 사용하기 때문에 다른 Retrofit 인스턴스에 들어갈 converter와 헷갈리지 않기 위해서 @Named 어노테이션으로 구분하였다.
나는 Retrofit Builder에는 @Named로 구분하였지만 converter를 구분하지 않아 json의 converter, xml converter 중 어떤것을 사용해야 할 지 Hilt에서 파악하지 못하는 오류를 겪었다. (동일한 Converter.Factory 타입이기 때문이다.)
따라서 @Named 어노테이션으로 구분하면 Hilt가 제대로 파악 할 수 있다.
이렇게 하면 XML 데이터를 성공적으로 받아올 수 있다.
RecyclerView에 XML 데이터 띄우기
우선 DiffUtil을 활용해 RecyclerView Adapter를 생성해준다.
이 후 viewModel에서 서버통신을 통해 받아온 데이터를 fragment에서 관찰 한 뒤 값의 변경이 발생하면 해당 값을 adapter.submit(it) 으로 adpater에 넘겨준다. (아래는 fragment 코드이다.)
private fun observeNewsData(){
viewModel.newsResponse.observe(viewLifecycleOwner) {
newsAdapter.submitList(it)
}
}
private fun initAdapter() {
newsAdapter = NewsAdapter {
Intent(Intent(requireContext(), NewsActivity::class.java)).apply {
startActivity(this)
}
}
binding.rvTodayNews.adapter = newsAdapter
}
onViewCreated() 에서 해당 메서들을 실행시켜주면 RecyclerView에 잘 뜨는것을 볼 수 있다.
마치며
XML 데이터를 가져오는 과정에서 많은 삽질을 했다. 가장 중요한 것은 DTO 작성이니 데이터가 제대로 들어오지 않는다면 이 부분을 다시 한번 확인하는게 필요한 것 같다. 여러 공공 API는 XML 데이터 만을 지원하는 경우도 있으니 해당 포스팅이 도움이 되길 바란다.
'Android > 프로젝트 개발' 카테고리의 다른 글
[Android] PDF 뷰어 띄우기 / Activity 내에 PDF 뷰어 넣기 feat. 파일관리 (0) | 2024.09.20 |
---|---|
[Android/JAVA] Zxing 활용 바코드/QR 스캐너 구현하기 (0) | 2024.08.07 |
[Android] 인앱 업데이트 구현하기 / 인앱 업데이트 테스트 (1) | 2023.11.01 |
[Android] registerForActivityResult로 팝업 Activity를 만들어보자 (0) | 2023.08.26 |
[Android] 안드로이드 MaskFilter로 blur 효과 구현하기 (0) | 2023.08.07 |