この記事で言いたいこと
RoomとRxJavaを使うとき、AndroidXじゃない方のバージョンを使うと、SELECTだけしかRxで返してもらえないので注意。
Roomの操作はRxJavaで扱える
RoomとRxJava2を連携するライブラリが標準で提供されていて、Googleの公式ドキュメントに導入方法から使い方まで解説されています。
https://developer.android.com/training/data-storage/room/accessing-data#query-rxjava
で、こんな風にDaoの各メソッドの返り値を指定するだけで、勝手にRxと連携してくれます。
(公式ドキュメントよりコピペ)
@Dao
interface MyDao {
@Query("SELECT * from user where id = :id LIMIT 1")
fun loadUserById(id: Int): Flowable<User>
// Emits the number of users added to the database.
@Insert
fun insertLargeNumberOfUsers(users: List<User>): Maybe<Int>
// Makes sure that the operation finishes successfully.
@Insert
fun insertLargeNumberOfUsers(varargs users: User): Completable
/* Emits the number of users removed from the database. Always emits at
least one user. */
@Delete
fun deleteAllUsers(users: List<User>): Single<Int>
}
こんな感じでクエリ結果やInsertの結果を様々な型で扱うことができます。
でもAndroidXはまだalphaとかバージョンについてるし、安定版を使いたいなあ、と思ったとき、Xじゃないプレーンなプロジェクトの方を使うこともできます。
https://developer.android.com/topic/libraries/architecture/adding-components#room
これをみると、バージョン1.1.1がandroid.arch.persistence.room
に用意されているようです。
罠
という感じでPre-AndroidXのバージョンを使ってもりもりコードを書いていったところ、次のようなエラーで怒られました。
Entities and Pojos must have a usable public constructor. You can have an empty constructor or a constructor whose parameters match the fields (by name and type)
謎すぎます。
KotlinならEntityはData Classで定義するので、ここで注意されているようなコンストラクタが無いわきゃないです。
試しに空のコンストラクタをわざわざ用意してみましたが、解決しませんでした。
いろいろ試行錯誤した結果、バージョン1.1.1ではInsert, Delete, Updateの返り値にRxを指定できないことがわかりました。
(実はさっきの公式ドキュメントにもそう書いてあるのですが…)
https://medium.com/androiddevelopers/room-rxjava-acb0cd4f3757
https://issuetracker.google.com/issues/63317956
バージョン1.1.1で Insert や Delete に Completable や Single は使えません。
それらを指定したとき、さっきのようなエラーが出たりするようです。(たぶん)
1.1.1で使うには
なので1.1.1ではInsertやDeleteの返り値はLongやUnitを指定することになります。
でも、できればRxで統一的に扱いたいわけです。
AndroidXを素直に使えというのはそのとおりですが、まあそうしないでもやっていくことはできます。
たとえば、DatabaseクラスにRxオブジェクトを返すメソッドを用意するなどが便利そうです。
@Database(entities = [Img::class], version = 1)
abstract class ImogeDatabase : RoomDatabase() {
abstract fun imgDao(): ImgDao
// たとえばDaoにdelete()が用意されているとして
fun deleteAsCompletable(imoge: Img) = Completable.fromCallable {
imgDao().delete(imoge)
}
}
こういうメソッドを用意しておいて、Daoのdelete()
は使わずに、このdeleteAsCompletable()
を使うようにするとか。
でも本当はDatabaseクラスじゃなくて、Daoインターフェース自体にデフォルト実装をもつメソッドを用意したいんですよね…
SelectはDaoから呼ぶけど、InsertやDeleteはDatabaseから呼ぶ、とか使いづらいの極みでしかない。
けど、それはうまくいきません。
Roomが自動生成するコードがKotlinじゃなくてJavaっぽくて、Interfaceのデフォルト実装をうまく扱ってくれないんですよね…(参考)
誰かいい解決法を授けてください。
おたのみもうします。