RoomでDBの値更新時に、更新通知がこない!というときはDIの設定に原因があるかも

「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()

まとめ

なんか当たり前のことしか書いてない気がしますが、生成されたコードだったりを深く読み解くのも大事ですが、基本的なところもしっかり確認していきましょう。。ということで。 自戒しかないメモでした。