できたもの
【2017/01/09追記】
約一年放置していましたが、本家での対応はまだみたいなので、2.2.2 に対応したバージョンを公開しました。
経緯
開発中のアプリにRealmを導入しようとして、サンプルに驚きました。
たとえばequalToで絞り込もうとするとこうなります。
open class Cat : RealmObject() {
@Required
open var name: String = ""
}
Realm.getInstance(context).use {
var cats = it.where(Cat::class.java)
.equalTo("name", "たま") // (1)
.equalTo("name", false) // (2)
.findAll()
}
問題点はふたつ。
- name というフィールド名をベタ書きすることになる
- name の型をコンパイル時に判定しないので、"たま"のところに 1 とか false とかも書けてしまう(実行時例外)
これを解決したいと思った時に思い出したのが、S2JDBCのタイプセーフAPIです。
http://s2container.seasar.org/2.4/ja/s2jdbc_typesafe.html
S2JDBCの実装と合わせるならRealmQuery側に受け取り口が必要ですが、Kotlinならさくっとメソッドを追加できます。
どうせなら、RealmQueryにタイプセーフなメソッドを直接生やしてみよう。
ということで作ってみた。
ライブラリの内容
やってることは以下の通り。
(1) APTで @RealmClass
が付与されているクラスのフィールド名クラスを自動生成
なお、@RealmClass
は RealmObject に指定されているので、何もしなくて大丈夫です。
// Generated class
public class CatNames {
public static KRequiredStringPropertyName name() {
return new KRequiredStringPropertyName("name");
}
}
(2) Kotlinの拡張関数で、タイプセーフなメソッドを追加
fun <E : RealmObject> RealmQuery<E>.equalTo(fieldName: KRequiredPropertyName<String>, value: String) : RealmQuery<E> = equalTo(fieldName.name(), value)
// @Requiredなしの場合はこっち(valueがnull可)
fun <E : RealmObject> RealmQuery<E>.equalTo(fieldName: KNullablePropertyName<String>, value: String?) : RealmQuery<E> = equalTo(fieldName.name(), value)
こんな感じになりました。
Realm.getInstance(context).use {
var cats = it.where(Cat::class.java)
.equalTo(CatNames.name(), "たま") // nameって自分で書く必要が無い
.equalTo(CatNames.name(), false) // String以外はコンパイルエラー
.equalTo(CatNames.name(), null) // nameはnull不可なのでコンパイルエラー
.findAll()
}
RealmQueryのメソッドは一通り実装してみたので、よかったらどうぞ。
https://github.com/75py/KotliNames
補足 公式サポートは?
タイプセーフAPIについては、RealmのIssueにも上がっているものの、「現時点では優先度が低い」ということらしいです。
https://github.com/realm/realm-java/issues/1744#issuecomment-155158923
KotliNamesはJavaでも使えるようにしてありますが、拡張関数なしではせいぜい タイポ対策にしかなりません。
ベタ書きよりはマシですが。
Javaでのタイプセーフに関しては、公式にサポートされるのを待ったほうが賢明かと思います。
(一応kotlinamesjにJava向けコードを置く余地は残しているので、気が向いたら書くかもしれませんが。)
補足 RealmObjectをKotlinで書く場合の注意点
RealmObjectをKotlinで書く場合はいろいろ注意が必要です。
Javaに変換されたときどうなるかを必要以上に意識することになりますので、おとなしくJavaで書いておいた方が無難かもしれません。
// KotlinではデフォルトがJavaで言うfinalなので、クラス・フィールドにopen指定が必要
// 【2017/01/09追記】 1.0.6で All-open compiler plugin が公開されたので、今後はこれを使うのが良いでしょう
open class Cat : RealmObject() {
@Required
open var name: String = ""
// Javaだと public String name;
// 特に問題なく動いているっぽい
open var age: Int = 0
// Javaだと public int age;
// プリミティブ型になるので、@RequiredなしでNULL不可になる
open var weight: Double? = 0.0
// Javaだと public Double weight;
// これはNULL可
@Required
open var hoge: Int = 0
// int型になるので、「プリミティブ型には@Requiredつけられないよ!」とRealm先生に怒られる
}
その他言いたいこと
- Kotlinかわいいよー 【2017/01/09追記】 Dagger2との併用は茨の道(自力とググり力が試される)