I am trying to ensure that my database always contains an initial row. I read through How to populate Android Room database table on first run? and the main thing that I'm running into is that I have no instance to access (or I don't know how to access it?) using Hilt when I'm creating the database. If I try re-using the provideDatabase
Hilt method I've written, it results in SQLite Database Leaks (presumably because there's no one around to close the database using those spawned instances). Here's my code:
@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()
}
}
So, how do I get a hold of my DAO to do the initialization? Do I need to just manually craft an insert statement in SQL and call it on the database?
Your provideDatabase
method always creates a new instance whenever it is called: Dagger makes it a singleton by only calling that method once. The only way to get the singleton GameDatabase
instance managed by ApplicationComponent
is to request it as a dependency. Since GameDatabase
will need to depend on itself via GameDao
, this is a circular dependency.
To resolve a circular dependency in Dagger, you can depend on a 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()
}