Kotshi+MVVMをカジュアルに採用してみた。
GWも過ぎて、ハイテンションで迎えた令和もすでに鬱々とした気分で過ごす日々です。
今回はKotlinでアウトプットする機会があったので、便利ライブラリを簡単に紹介します。
ネイティブアプリケーション開発においては、バックエンド側のWeb-APIをRailsなどで用意することが多く、Kotlinで開発するクライアント側はリクエストを送信し、レスポンスに乗っているJsonを整形することで表示するデータを取得します。
しかし、null許容を搭載しているKotlinにおいてはKotlinJsonAdapterFactoryなるものを追加しない場合に空のJsonをパースする際にぬるぽが発生したり、
Moshiなるアダプターを使用するもののリフレクションを搭載している為に解釈が遅いといった問題があります。
Jsonを解析する時の問題
まだこれらの利点を払拭するような実感は得ていませんが、今回はKotshiというライブラリを使用することにしました。
よくある画像とコメントをリスト形式でレスポンスするAPIを例に、下記のようなクラスを実装しています。
Response
{
params: {
total: 100, # 総レコード数
offset: 50, # オフセット
limit: 5 # リクエスト時指定の取得数
},
album_records: [
{
"image_url": "https://album-records.herokuapp.com/images/hoge.jpg",
"comment": "Hoge",
"tag": "programming",
"recorded_at": "2019-05-10 00:04:00"
},
{
"image_url": "https://album-records.herokuapp.com/images/fuga.jpg",
"comment": "Fuga",
"tag": "programming",
"recorded_at": "2019-05-10 10:12:50"
},
...
Client
// 実際にJsonを解析するアダプタークラス
@KotshiJsonAdapterFactory
data class Album(
@field:Json(name = "params") var params: GetDataParams,
@field:Json(name = "album_records") var albumRecords: List
) {companion object {
val INSTANCE: AlbumRecord.Companion = AlbumRecord
@ToJson fun toJson(writer: JsonWriter, value: AlbumRecord, stringAdapter: JsonAdapter) {
stringAdapter.toJson(writer, value)
}
}
}// 取得したいインナークラス(album_recordsの中身)
data class AlbumRecord(
val image_url: String?,
val comment: String,
@Json(name = "tag") val tag: String?,
val recorded_at: String?
)// リクエスト時のパラメータ(今回は使わない部分)
@JsonSerializable
data class GetDataParams(
val total: String,
val offet: String,
val limit: String
)// レスポンスを格納するViewModel
class AlbumRecordViewModel: ViewModel() {
val albumRecoLiveData = MediatorLiveData>()
}
@field:Json(name = "params") のように指定することで、Jsonを解析してくれる名前を与える事が出来、後置の名前でクラスのメンバとして振る舞います。
Kotlinのdata classはtoStringなどが備わっているので、パースで入り用の際にはデータを処理するメソッドを用意する手間を無くし、憂鬱な気持ちも軽減してくれます。
今回はリスト形式のオブジェクトが含まれていた為に、クラスを切り分けつつうまく解釈してくれるような構造にしました。不必要な部分については無視してくれる便利なアノテーションも存在しますが、下記のエラーに対応していて実装はしませんでした。
Exception in thread "main" java.lang.IllegalStateException: Expected a string but was BEGIN_OBJECT at line 1 column 16 path $.response
https://stackoverflow.com/questions/41649731/gson-expected-a-string-but-was-begin-object
つまりはStringとして期待されたメンバはオブジェクトでやってきちゃったよ!
どうしろ言うんじゃい!!!
と言うKotlin(もといJava)様の叫びです。
これは上のようにparamsとalbum_recordを切り分けずにひとつのクラスで解析しようとしていたところ頻発して、メンバ宣言の位置変えをしてみてもうまくいきませんでした。
で、実際に解析をしたいメンバを連ねるだけではダメで。
companionに書かれている専用解析変数&メソッドが鍵となります。
解析したいクラスの情報を渡して、そこではどんな感じにパースすんのと言うメソッドもimplementします。(書かなければ@FromJsonか@ToJson実装しないとこれ使えへんでと言われる)
リクエストをどうするか問題
今回リクエストに使うライブラリはOkHttp3にしてみました。触った事なかったから。
こいつはGETする時にはパラメータを変えたいなと思った時に、インスタンスがイミュータブルなもので、もうひとつ新たなものを用意しないと調整ができないと言うものでした。
Web触っていた時にはerbさんが非同期でリクエスト取ってきてくれていたものだけど、今回扱ってるものは小さなパソコン、Androidなので極力パフォーマンスも考慮して実装していきたい。
そこで画面回転があろうとインスタンスを死守してくれるViewModelさんの力を借りました。
プライベートアプリなのであらかじめ全てのデータを取得して、適当にページングして少しずつ見せる感じに。画像の表示はPicassoを使ったのでスマートにキャッシュを持ってくれて、表示もサクサクです。
諸事情でデモやコードは上げてないですが、許可が取れたら上げてみます。
今回の反省点はと言うと、なんだかんだと困ったらすぐに検索してしまい、ライブラリのソースコードをきちんと読むことを疎かにしていたって事です。ライブラリもコードなの、ああそういえば昔参加してる勉強会でひたすらコードリーディングしてる人いたけど、あれが正なのかなあ…
(イケてる開発者のトレンドはまだまだ理解できないよ…)