概要
JSON 以外のデータ形式を Retrofit で扱う方法について述べます。時間がない方は Retrofit の公式 を見てください。
前提
Retrofit2 の使い方については知っているものとします。
なぜやるのか?
趣味で RSS リーダーを自作したいと思った際に、XML を Retrofit で扱うにはどうしたら良いのかとわからなくなり、だったら自分で実装しようと思ってやってみたので、それを残しておきます。
RSS は XML の派生形式です。JSON ではありません。かつては、Retrofit で XML を扱う際は Simple XML を使うのが一般的のようでした。
Deprecation
……が、いま SimpleXmlConverterFactory をコードに書くと Deprecated の警告が出ます。
@deprecated we recommend switching to the JAXB converter.
「JAXB の Converter を使いましょう」とのことです。
does not work on Android.
ではと思って JAXB Converter を見に行くと、README に以下の記述があります。
Note that JAXB does not work on Android.
ちょっと何て書いてあるか英語がよくわからないですね。 Android で XML を処理したいという開発者のニーズは存在しなくなったのでしょうか?
Issue
どうしたらよいのかと思って調べたら、別に悩んでいる人がいました。
……が、特に解決策があるわけでもないようです。
Solution?
もうちょっと探したら、以下のQ&Aを発見しました。 Tickaroo TikXML というものがあるらしいです。
What kind of XmlConverter can I use for Retrofit in Android?
GitHub Repository
Retrofit2 用の Converter も用意されています。
implementation 'com.tickaroo.tikxml:retrofit-converter:0.8.15'
普通の方はこれを用いると良いでしょう。
My solution?
上の Converter の導入が上手くいかないので諦め、ほかにどうしたらよいのかと思って調べたら、
faruktoptas/RetrofitRssConverterFactory という Converter を見つけました。 これを見ると、自分で Converter を作ればどんなレスポンスでもオブジェクトに Mapping できるようです。
RSS のパースに関しては過去に自分で作った Parser があるので、せっかくだからそれを使ってみました。
Official solution
これは大発見でも何でもなく、 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 を特定の型に変換します。
この処理を変えることで、あらゆるデータ形式を扱うことができるようになります。
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 のインスタンスを生成するファクトリーを返すクラスです。
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 の普通の使い方については多数の記事があるので、ここでは特に触れません。
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 インターフェイスはこんな感じです。
interface RssService {
@GET
fun call(@Url url: String): Call<Rss?>
}
おわりに
JSON 以外のデータ形式、今回は XML を Retrofit で扱う方法について述べました。
で、これを知って何の役に立つの?
自分で Converter を作れば、どんなデータ形式でも(CSVやYAML等々)扱うことができるということだけ知っておけば良いかと思います。
新しいデータ形式や社内独自のデータ形式も Retrofit で扱えて幸せになるかもしれません。
そして、メジャーなデータ形式であれば、誰かが Converter を作っているかもしれないので、それを GitHub から探し出した方が早いかもしれない、ということを知っておくのも大事なことです。