Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the proper way to implement .addCallback() when providing RoomDatabase via Dagger 2?

I'm using Dagger 2 to create and share my RoomDatabase where necessary in my app.

I'm trying to implement addCallback() so I can override the database's onCreate() function and use it to insert my initial database values. This is where I'm running into issues.

I feel like I have to be overlooking something obvious, but I can't figure out a way to do this gracefully.

RoomDatabase class:

@Database(
        entities = [Station::class],
        version = 1,
        exportSchema = false
)
abstract class TrainDB : RoomDatabase() {

    abstract fun stationDao() : StationDao

} 

DAO:

@Dao
abstract class StationDao {

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    abstract fun insert(stations: Station)

    @Query("SELECT * FROM station_table")
    abstract fun getAll() : LiveData<List<Station>>

}

Dagger Module:

@Module
class DataModule {

    @Singleton
    @Provides
    fun provideDb(app: Application): TrainDB {
        var trainDB: TrainDB? = null
        trainDB = Room
                .databaseBuilder(app, TrainDB::class.java, "train.db")
                .allowMainThreadQueries()
                .fallbackToDestructiveMigration()
                .addCallback(object : RoomDatabase.Callback() {
                    override fun onCreate(db: SupportSQLiteDatabase) {
                        super.onCreate(db)

                        /*
                        WHAT GOES HERE?
                        */

                    }
                })
                .build()
        return trainDB
    }

    @Singleton
    @Provides
    fun providesStationDao(db: TrainDB) : StationDao = db.stationDao()

}

I'd like to be able to access my DAO in the onCreate() callback. It seems obvious that this should be possible because Google is pushing Room and Dagger together and this is likely a pretty common use case.

I've tried providing the DAO as a constructor argument for provideDB(), but that creates a circular dependency

I've tried initializing my RoomDatabase as a companion object. Then instead of using the Room.builder format in my provideDB() method, I can call a getInstance() method that has access to the DAO. But this way I'm met with an error for a recursive call to getWriteableDatabase().

I understand that I can use something like db.execSQL(), but it seems like such a shame to do that when I'm using Room.

Is there a better way that I'm missing? I'm using Kotlin, but Java examples are welcome. :)

like image 442
Chris Feldman Avatar asked May 25 '18 02:05

Chris Feldman


2 Answers

You can create final one-element array.

@AppScope
@Provides
public AppDatabase appDatabase(@ApplicationContext Context appContext, AppExecutors executors) {

    final AppDatabase[] databases = new AppDatabase[1];

    databases[0] = Room.databaseBuilder(appContext, AppDatabase.class, AppDatabase.DATABASE_NAME)
            .fallbackToDestructiveMigration()
            .addCallback(new RoomDatabase.Callback() {
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) {
                    super.onCreate(db);
                    executors.diskIO().execute(() -> {
                        databases[0].populateDatabaseOnCreate();
                    });
                }
            }).build();
    return databases[0];
}
like image 200
michal3377 Avatar answered Nov 14 '22 06:11

michal3377


I have managed it like this:

@Module
class DataModule {

lateinit var trainDB: TrainDB

@Singleton
@Provides
fun provideDb(app: Application): TrainDB {
    trainDB = Room
            .databaseBuilder(app, TrainDB::class.java, "train.db")
            .allowMainThreadQueries()
            .fallbackToDestructiveMigration()
            .addCallback(object : RoomDatabase.Callback() {
                override fun onCreate(db: SupportSQLiteDatabase) {
                    super.onCreate(db)

                    /*

                    trainDB.stationDao().insert(...)


                    */

                }
            })
            .build()
    return trainDB
}

@Singleton
@Provides
fun providesStationDao(db: TrainDB) : StationDao = db.stationDao()

}

But remember You need to to do a fake read from the database in order to initiate the db and invoke onCreate(). don't write into db as your first interact when db hasn't been created because it will create a race condition and your on create writing won't take effect.

like image 45
Amir Avatar answered Nov 14 '22 05:11

Amir