AndroidからXMLデータ形式のWeb APIを使う

# 概要

近年ではWeb APIのデータ形式といえばほぼJSONが使われていますが、最近XMLを使う機会があったので、そのAndroid実装についてメモを残しておきます。

# 要件

今回、実装に当たって以下のような要件で考えていました。

* Androidで動かす
* Kotlinで実装する
* Retrofit2から利用する (Converter.Factoryが必要)
* Converterのコードとか書きたくない

つまるところ、最近のよくある実装と同じ書き方で楽に実装したかったということです。

# ライブラリ

XMLシリアライザの有名所としては[SimpleXML](http://simple.sourceforge.net/home.php)があり、Webを調べるとよく名前が挙がります([GitHub](https://github.com/ngallagher/simplexml))。[Converterもある](https://github.com/square/retrofit/tree/master/retrofit-converters/simplexml)ので一見良さそうに見えますが、ライブラリに脆弱性があり、メンテナンスもされていないので、現在では非推奨となっています。(実装しようとすると、Deprecated警告が出てきます。)

調べて見ると、現在は[TikXML](https://github.com/Tickaroo/tikxml)を使うのが良いとのことです。メンテナンスも継続して行われていますし、Converterも提供されているため、Retofitからの利用も問題ありません。

# 実装例

Android Studioを使って、TikXMLの実装を行ってみました。

## ライブラリの導入

build.gradleからライブラリを導入します。(以下、それぞれを適切な場所に記述する)

```gradle
apply plugin: 'kotlin-kapt'
// :
    ext.tikxml_version = '0.8.13'
// :
    implementation "com.tickaroo.tikxml:annotation:$tikxml_version"
    implementation "com.tickaroo.tikxml:core:$tikxml_version"
    implementation "com.tickaroo.tikxml:retrofit-converter:$tikxml_version"
    kapt  "com.tickaroo.tikxml:processor:$tikxml_version"
```

## APIデータモデルの定義

ここでは、音楽データベース[MusicBrainz](https://musicbrainz.org/)のAPIを利用してみます。(ちなみに、MusicBrainzはJSON形式のAPIも提供しているので、特にXMLを利用する必要はないです。あくまでもサンプルとして使っています。)

https://musicbrainz.org/ws/2/artist/c999f4ef-8733-46f8-ae3a-d5628c260ea8

このURLを叩くと、次のXMLが取得できます。(整形済み)

```xml
<metadata xmlns="http://musicbrainz.org/ns/mmd-2.0#">
    <artist id="c999f4ef-8733-46f8-ae3a-d5628c260ea8" type="Group" type-id="e431f5f6-b5d2-343d-8b36-72607fffb74b">
        <name>Falcom Sound Team jdk</name>
        <sort-name>Falcom Sound Team jdk</sort-name>
        <country>JP</country>
        <area id="2db42837-c832-3c27-b4a3-08198f75693c">
            <name>Japan</name>
            <sort-name>Japan</sort-name>
            <iso-3166-1-code-list>
                <iso-3166-1-code>JP</iso-3166-1-code>
            </iso-3166-1-code-list>
        </area>
    </artist>
</metadata>
```

これをKotlinのData classに落とし込むと、次のようになります。
各アノテーションは次のような意味になります。

* Xml 要素
* Element 子要素
* PropertyElement 要素の内容
* Annotation 属性

``` SampleData.kt

import com.tickaroo.tikxml.annotation.Attribute
import com.tickaroo.tikxml.annotation.Element
import com.tickaroo.tikxml.annotation.PropertyElement
import com.tickaroo.tikxml.annotation.Xml

@Xml(name = "metadata")
data class MetadataRes(
    @Element(name="artist")
    val artist: ArtistRes
)

@Xml(name = "artist")
data class ArtistRes(
    @Attribute(name = "id")
    val id: String,
    @Attribute(name = "type")
    val type: String,
    @Attribute(name = "type-id")
    val typeId: String,
    @PropertyElement(name="name")
    val name: String,
    @PropertyElement(name="sort-name")
    val sortName: String,
    @PropertyElement(name="country")
    val country: String,
    @Element(name="area")
    val area: AreaRes
)

@Xml(name = "area")
data class AreaRes(
    @Attribute(name = "id")
    val id: String,
    @PropertyElement(name="name")
    val name: String,
    @PropertyElement(name="sort-name")
    val sortName: String,
    @Element(name="iso-3166-1-code-list")
    val iso: Iso3166CodeListRes
)

@Xml(name = "iso-3166-1-code-list")
data class Iso3166CodeListRes(
    @PropertyElement(name = "iso-3166-1-code")
    val iso31661Code: String
)
```

## 動かしてみる

Retrofit用インターフェースの作成。この辺はJSONと変わりません。
(User-AgentはMusicBrainzのAPIアクセスに必要となります。)

``` SampleApi.kt
interface SampleApi {

    @Headers("User-Agent: Test Program")
    @GET("artist/{mbid}")
    suspend fun getArtist(@Path("mbid") mbid: String): MetadataRes

}
```

以下、OkHttpとRetrofitを使ってとりあえず動かす雑な実装。

``` SampleActivity.kt
    suspend fun getArtist() {
        val client = OkHttpClient.Builder().build()
        val retrofit = Retrofit.Builder()
            .baseUrl("https://musicbrainz.org/ws/2/")
            .client(client)
            .addConverterFactory(TikXmlConverterFactory.create())
            .build()
        val api: SampleApi = retrofit.create(SampleApi::class.java)
        val data = api.getArtist("c999f4ef-8733-46f8-ae3a-d5628c260ea8")
        Log.d("SAMPLE", data.toString())
    }
```

次の通りログが出力され。無事データが取得できていることが確認できました。

```terminal
MetadataRes(artist=ArtistRes(id=c999f4ef-8733-46f8-ae3a-d5628c260ea8, type=Group, typeId=e431f5f6-b5d2-343d-8b36-72607fffb74b, name=Falcom Sound Team jdk, sortName=Falcom Sound Team jdk, country=JP, area=AreaRes(id=2db42837-c832-3c27-b4a3-08198f75693c, name=Japan, sortName=Japan, iso=Iso3166CodeListRes(iso31661Code=JP))))
```

# 最後に

Retrofit + TikXMLで問題なくXML形式のWeb APIからデータを取得することができました。
XMLは要素の属性値があるので、その辺りをモデルに落とし込む必要があるのがJSONと大きく異なる点ですが、アノテーションで良い感じに定義できるので、あまり違いを感じずに済むのでとても楽でした。

ちなみに、TikXMLの前にSimpleXMLでも試しに実装してみましたが、Data classのプロパティはvarとして初期値を与える必要がある(コンストラクタを使ってくれない)など、Kotlinから扱う上で不都合が多いので、現在において使う必要は無さそうだなと感じました。

ついでに全くの余談になりますが、これを調べる過程でXMLに関する情報がJSONに比べて非常に少ないことに気づき、もう既にXMLはWeb APIのデータ形式としてあまり使われていないのか…ということを実感しました。

# 参考

* [Retrofitでxmlの値を取得する \- Qiita](https://qiita.com/kumas/items/df43680e1c5f655c0424)
* [JSON 以外のデータ形式を Retrofit で扱う\(なら Converter を実装する\) \- Qiita](https://qiita.com/toastkidjp/items/8a699c4c8ca38f0081f3)