通过 Dagger 2 提供 RoomDatabase 时实现 .addCallback() 的正确方法是啥?

Posted

技术标签:

【中文标题】通过 Dagger 2 提供 RoomDatabase 时实现 .addCallback() 的正确方法是啥?【英文标题】:What is the proper way to implement .addCallback() when providing RoomDatabase via Dagger 2?通过 Dagger 2 提供 RoomDatabase 时实现 .addCallback() 的正确方法是什么? 【发布时间】:2018-11-04 08:38:12 【问题描述】:

我正在使用 Dagger 2 在我的应用中根据需要创建和共享我的 RoomDatabase。

我正在尝试实现addCallback(),以便我可以覆盖数据库的onCreate() 函数并使用它来插入我的初始数据库值。这就是我遇到问题的地方。

我觉得我必须忽略一些显而易见的事情,但我想不出一种优雅的方法。

RoomDatabase 类:

@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>>


匕首模块:

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


我希望能够在 onCreate() 回调中访问我的 DAO。很明显这应该是可能的,因为 Google 正在将 Room 和 Dagger 放在一起,这可能是一个非常常见的用例。

我尝试将 DAO 作为 provideDB() 的构造函数参数,但这会产生循环依赖

我尝试将我的 RoomDatabase 初始化为伴随对象。然后,我可以调用可以访问 DAO 的 getInstance() 方法,而不是在我的 provideDB() 方法中使用 Room.builder 格式。但是这样我在递归调用getWriteableDatabase() 时遇到了错误。

我知道我可以使用 db.execSQL() 之类的东西,但是当我使用 Room 时这样做似乎太可惜了。

我错过了更好的方法吗?我正在使用 Kotlin,但欢迎使用 Java 示例。 :)

【问题讨论】:

这个答案看起来不错:Prepopulate Room Database with Dagger 2 in Kotlin 在我看来,使用协程是一个更好的解决方案。一个很好的例子raywenderlich.com/… 【参考方案1】:

我是这样管理的:

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


但请记住,您需要从数据库中进行假读取以启动数据库并调用 onCreate()。当 db 尚未创建时,不要在第一次交互时写入 db,因为它会产生竞争条件,并且您的 on create 写入不会生效。

【讨论】:

+1 表示“您需要从数据库进行虚假读取以启动 db 并调用 onCreate()。当 db 没有时,不要在第一次交互时写入 db被创建是因为它会创建一个竞争条件,而你的 on create 写入不会生效。” onCreate() 在第一次安装应用程序时只会被调用一次。如果您希望再次调用它,您必须从您正在使用的模拟器或手机中卸载该应用程序并重新安装。无需假读。 @Jeffrey,是的,安装应用程序后,它不会到达这一行,除非您询问 DI 您需要此依赖项(假设是假读取)然后 DI 联系到这一行并尝试创建数据库的一个实例,因为没有其他实例,它在创建一次时进行,之后,您可以访问 Dao 进行读取和写入 在创建文档中说会在创建所有表后调用,但在我的情况下,它会在没有表时调用,而且数据库版本为 0?!【参考方案2】:

您可以创建最终的单元素数组。

@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];

【讨论】:

【参考方案3】:

你可以在java中做到这一点

    AppDatabase appDatabase = null;

    AppDatabase finalAppDatabase = appDatabase;
    appDatabase = Room.databaseBuilder(MyApplication.getApplication(),
            AppDatabase.class, Constants.DATABASE_NAME).
            addCallback(new RoomDatabase.Callback() 
                @Override
                public void onCreate(@NonNull SupportSQLiteDatabase db) 
                    super.onCreate(db);
                    //check for null
                    finalAppDatabase.yourDao();
                
            ).
            build();
     return appDatabase;

【讨论】:

我尝试了与此代码等效的 Kotlin。我还将我的文件转换为 Java,所以我可以直接使用它。在这两种情况下,我都会在 finalAppDatabase 上得到一个空引用。【参考方案4】:

我们可以通过惰性注入来防止循环依赖。

例如

    @Singleton
    @Provides
    fun provideDb(
        app: Application,
        trainDBLazy: Lazy<TrainDB> // import dagger.Lazy
    ): TrainDB 
        return Room
                .databaseBuilder(app, TrainDB::class.java, "train.db")
                .allowMainThreadQueries()
                .fallbackToDestructiveMigration()
                .addCallback(object : RoomDatabase.Callback() 
                    override fun onCreate(db: SupportSQLiteDatabase) 
                        super.onCreate(db)
                        var trainDB = trainDBLazy.get()
                    
                )
                .build()
    

【讨论】:

以上是关于通过 Dagger 2 提供 RoomDatabase 时实现 .addCallback() 的正确方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

kotlin 缺少提供程序的 Dagger 2 错误

Dagger 2,在模块中提供应用程序上下文

使用dropwizard-加入DI-dagger2

Dagger 2 错误:依赖“不能在没有 @Inject 构造函数的情况下提供”,而它实际上是用 @Inject 注释的

kotlin + Dagger2 :没有@Provides-annotated 方法就无法提供

如何使用 Dagger 2 向 MyFirebaseMessagingService 提供数据库,以便我可以在 android 本地存储 fcm 消息