JSON 以外のデータ形式を Retrofit で扱う(なら Converter を実装する)

# 概要
JSON 以外のデータ形式を Retrofit で扱う方法について述べます。時間がない方は [Retrofit の公式](https://square.github.io/retrofit/) を見てください。

## 前提
Retrofit2 の使い方については知っているものとします。

## なぜやるのか？
趣味で RSS リーダーを自作したいと思った際に、XML を Retrofit で扱うにはどうしたら良いのかとわからなくなり、だったら自分で実装しようと思ってやってみたので、それを残しておきます。

RSS は XML の派生形式です。JSON ではありません。かつては、Retrofit で XML を扱う際は Simple XML を使うのが一般的のようでした。

### Deprecation
……が、いま SimpleXmlConverterFactory をコードに書くと Deprecated の警告が出ます。

[リポジトリーを見てみる](https://github.com/square/retrofit/blob/master/retrofit-converters/simplexml/src/main/java/retrofit2/converter/simplexml/SimpleXmlConverterFactory.java#L34)と、

> @deprecated we recommend switching to the JAXB converter.

「JAXB の Converter を使いましょう」とのことです。

### does not work on Android.
ではと思って [JAXB Converter](https://github.com/square/retrofit/tree/master/retrofit-converters/jaxb) を見に行くと、README に以下の記述があります。

> Note that JAXB does not work on Android.

~~ちょっと何て書いてあるか英語がよくわからないですね。~~ Android で XML を処理したいという開発者のニーズは存在しなくなったのでしょうか？

### Issue
どうしたらよいのかと思って調べたら、[別に悩んでいる人がいました](https://github.com/mitchtabian/Reddit-RSS-App/issues/1)。
……が、特に解決策があるわけでもないようです。

### Solution?
もうちょっと探したら、以下のQ&Aを発見しました。 Tickaroo TikXML というものがあるらしいです。
[What kind of XmlConverter can I use for Retrofit in Android?](https://stackoverflow.com/questions/53774071/what-kind-of-xmlconverter-can-i-use-for-retrofit-in-android)

#### GitHub Repository
[Tickaroo/tikxml](https://github.com/Tickaroo/tikxml)

Retrofit2 用の Converter も用意されています。

```groovy
implementation 'com.tickaroo.tikxml:retrofit-converter:0.8.15'
```

普通の方はこれを用いると良いでしょう。

### My solution?
上の Converter の導入が上手くいかないので諦め、ほかにどうしたらよいのかと思って調べたら、
[faruktoptas/RetrofitRssConverterFactory](https://github.com/faruktoptas/RetrofitRssConverterFactory) という Converter を見つけました。 これを見ると、自分で Converter を作ればどんなレスポンスでもオブジェクトに Mapping できるようです。

RSS のパースに関しては過去に自分で作った Parser があるので、せっかくだからそれを使ってみました。

### Official solution
これは大発見でも何でもなく、 [Retrofit の公式](https://square.github.io/retrofit/) にちゃんと書いてありました。

> If you need to communicate with an API that uses a content-format that Retrofit does not support out of the box (e.g. YAML, txt, custom format) or you wish to use a different library to implement an existing format, you can easily create your own converter. Create a class that extends the Converter.Factory class and pass in an instance when building your adapter.

というわけで、HTTP 通信で JSON 以外のデータをやり取りするアプリでは、 Retrofit の Converter をがんばって作りましょう。もしくは JSON に置き換えましょう。

# 実装

## Converter の実装
今回重要なのはここです。retrofit2.Converter インターフェイスを実装します。
`convert` メソッドで、受け取った OkHttp3 の ResponseBody を特定の型に変換します。
この処理を変えることで、あらゆるデータ形式を扱うことができるようになります。

```kotlin
class RssResponseConverter : Converter<ResponseBody, Rss> {

    private val parser = Parser()

    override fun convert(responseBody: ResponseBody): Rss? {
        val bodyString = responseBody.string()
        return parser.parse(bodyString.split(findSplitter(bodyString)))
    }

    private fun findSplitter(body: String) =
            if (body.contains(LINE_SEPARATOR)) LINE_SEPARATOR else "\n"

    companion object {
        private val LINE_SEPARATOR = System.getProperty("line.separator")
    }
}
```

### Parser
今回は勉強だと思ってゴリゴリのパーサーを自作しました。実際の開発は既存のライブラリーを用いましょう。

## Converter.Fatctory の実装
先ほど実装した Converter のインスタンスを生成するファクトリーを返すクラスです。

```kotlin
class RssConverterFactory private constructor(): Converter.Factory() {

    override fun responseBodyConverter(
            type: Type?,
            annotations: Array<Annotation>?,
            retrofit: Retrofit?
    ): Converter<ResponseBody, *> = RssResponseConverter()

    companion object {

        fun create() = RssConverterFactory()
    }
}
```

## Converter の利用
あとは addConverterFactory で自作の ConverterFactory を指定して、Retrofit を普通に使いましょう。Retrofit の普通の使い方については多数の記事があるので、ここでは特に触れません。

```kotlin
class RssReaderApi {

    private val converter = RssConverterFactory.create()

    @WorkerThread
    operator fun invoke(rssUrl: String): Rss? {
        val uri = rssUrl.toUri()
        val retrofit = Retrofit.Builder()
                .baseUrl("${uri.scheme}://${uri.host}")
                .addConverterFactory(converter)
                .build()

        val service = retrofit.create(RssService::class.java)
        val call = service.call(rssUrl)
        return call.execute().body()
    }
}
```

一応書いておくと RssService インターフェイスはこんな感じです。

```kotlin
interface RssService {

    @GET
    fun call(@Url url: String): Call<Rss?>
}
```

# おわりに
JSON 以外のデータ形式、今回は XML を Retrofit で扱う方法について述べました。

## で、これを知って何の役に立つの？
自分で Converter を作れば、どんなデータ形式でも(CSVやYAML等々)扱うことができるということだけ知っておけば良いかと思います。
新しいデータ形式や社内独自のデータ形式も Retrofit で扱えて幸せになるかもしれません。

そして、メジャーなデータ形式であれば、誰かが Converter を作っているかもしれないので、それを GitHub から探し出した方が早いかもしれない、ということを知っておくのも大事なことです。