使用 Hilt 预填充 Room 数据库,而无需创建额外的数据库实例

Pre-populating Room database with Hilt without creating an extra instance of the database

我试图确保我的数据库始终包含一个初始行。我通读了 How to populate Android Room database table on first run? 并且我 运行 的主要内容是我没有实例可以访问(或者我不知道如何访问它?)在我创建时使用 Hilt数据库。如果我尝试重新使用我编写的 provideDatabase Hilt 方法,它会导致 SQLite 数据库泄漏(大概是因为周围没有人使用这些生成的实例关闭数据库)。这是我的代码:

@Module
@InstallIn(ApplicationComponent::class)
object AppModule {

    @Singleton
    @Provides
    fun provideDatabase(@ApplicationContext context: Context): GameDatabase {
        return Room.databaseBuilder(context, GameDatabase::class.java, GameDatabase.GAME_DB_NAME)
            .addCallback(
                object : RoomDatabase.Callback() {
                    override fun onCreate(db: SupportSQLiteDatabase) {
                        super.onCreate(db)
                        // Initialize the database with the first game
                        ioThread {
                            provideDatabase(context).gameDao().createNewGame(Game())
                        }
                    }

                    override fun onOpen(db: SupportSQLiteDatabase) {
                        super.onOpen(db) 

                        // Ensure there is always one game in the database
                        // This will capture the case of the app storage
                        // being cleared
                        // THIS results in an instance being created that can't be closed - causing DB leaks!
                        ioThread {
                            val gameDao = provideDatabase(context).gameDao()

                            if (gameDao.gameCount() == 0) {
                                gameDao.createNewGame(Game())
                            }
                        }
                    }
                }
            ).build()
    }

    @Singleton
    @Provides
    fun provideGameDao(database: GameDatabase): GameDao {
        return database.gameDao()
    }
}

那么,我如何获得我的 DAO 来进行初始化?我是否需要在 SQL 中手动制作插入语句并在数据库中调用它?

有一个问题:

@Singleton
@Provides
fun provideGameDao(database: GameDatabase): GameDao {
    return database.gameDao()
}

应该是:

@Provides
fun provideGameDao(database: GameDatabase): GameDao {
    return database.gameDao()
}

您的 provideDatabase 方法总是在每次调用时创建一个新实例:Dagger 通过仅调用该方法一次使其成为单例。获取由 ApplicationComponent 管理的单例 GameDatabase 实例的唯一方法是将其作为依赖项请求。由于 GameDatabase 需要通过 GameDao 依赖自身,这是一个循环依赖。

要解决 Dagger 中的循环依赖,您可以依赖 Provider or Lazy:

    @Singleton
    @Provides
    fun provideDatabase(@ApplicationContext context: Context, gameDaoProvider: Provider<GameDao>): GameDatabase {
        return Room.databaseBuilder(context, GameDatabase::class.java, GameDatabase.GAME_DB_NAME)
            .addCallback(
                object : RoomDatabase.Callback() {
                    override fun onCreate(db: SupportSQLiteDatabase) {
                        super.onCreate(db)
                        // Initialize the database with the first game
                        ioThread {
                            gameDaoProvider.get().createNewGame(Game())
                        }
                    }

                    override fun onOpen(db: SupportSQLiteDatabase) {
                        super.onOpen(db) 

                        // Ensure there is always one game in the database
                        // This will capture the case of the app storage
                        // being cleared
                        // This uses the existing instance, so the DB won't leak
                        ioThread {
                            val gameDao = gameDaoProvider.get()

                            if (gameDao.gameCount() == 0) {
                                gameDao.createNewGame(Game())
                            }
                        }
                    }
                }
            ).build()
    }