「RoomでDBの値更新時に、更新通知がこない!」こんなことがもし起こったら、DI周りをしっかり見直しましょう、という話になります。
TL;DR
RoomDatabaseやDaoクラスがアプリ内の複数箇所で扱われている場合に、同一インスタンスが利用されているかを確認しましょう。 (特にDatabaseBuilderで作成するDatabaseをDIしている際などには、それがアプリ内でSingletonで扱われるようになっているかどうかを確認しましょう。)
たとえば、以下のような同一クラスのDaoなのだけど、以下の処理をそれぞれ別インスタンスで行ってしまうと、更新通知が即座に発火されなかったりします。
- (Insert / Updateなど)更新処理を行う
- LiveDataなどを返す関数を持つような、変更通知を行う
DIしている場合
特に、一つのDaoを複数箇所で利用する場合 and/or DIする場合などには気をつけましょう。
RoomDatabaseをセットアップしアプリをビルドすると、XXXDatabase_Implクラスのコードが生成されます。 このImplクラスは、daoを作成する関数を持ちます。たとえばLocalArticleDaoというDaoを扱うとして、コードをビルドすると以下のような関数が生成されます。
@Override public LocalArticleDao articleDao() { if (_localArticleDao != null) { return _localArticleDao; } else { synchronized(this) { if(_localArticleDao == null) { _localArticleDao = new LocalArticleDao_Impl(this); } return _localArticleDao; } } }
このように、XXXDatabase_Implクラスのコードを読むと、一度生成されたDaoはフィールドに保持され、以後、生成されたDaoを返すような実装になっていることがわかります。 なので、Databaseクラスがアプリ内でSingletonであれば、基本的には問題ないと考えます。
DI(ここではDagger)の話になりますが、以下のようにAppDatabaseをSingletonではない形でProvideしていると、 Injectされる箇所ごとに、新しいAppDatabaseが初期化され渡されます。DaoはAppDatabaseに紐づくに形で作成されるため、新しいAppDatabaseが都度渡されるということは、 複数箇所でInjectされているDaoも、すべて別インスタンスとなります。
@Provides // Singleton指定がないため、都度インスタンスが生成される fun provideAppDatabase(application: Context): AppDatabase { return Room.databaseBuilder( application.applicationContext, AppDatabase::class.java, "app-db" ) .build() } @Provides fun provideArticleDao(db: AppDatabase): LocalArticleDao = db.articleDao()
まとめ
なんか当たり前のことしか書いてない気がしますが、生成されたコードだったりを深く読み解くのも大事ですが、基本的なところもしっかり確認していきましょう。。ということで。 自戒しかないメモでした。
— Shohei Kawano (@_shoheikawano) March 22, 2020