3

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?

Chantell Osejo
  • 1,456
  • 15
  • 25

2 Answers2

6

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()
    }
Nitrodon
  • 3,089
  • 1
  • 8
  • 15
1

There's one problem:

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

should be:

@Provides
fun provideGameDao(database: GameDatabase): GameDao {
    return database.gameDao()
}
Martin Zeitler
  • 1
  • 19
  • 155
  • 216