使用Hilt预填充Room数据库后数据无法持久化的问题
问题:Room+Hilt预填充数据库后修改无法持久化
我通过Hilt配置的Room数据库,用onCreate从asset预填充数据,运行正常但修改后的数据无法持久化——修改后在应用前台/后台都能保留,但彻底清除后台重启后,所有数据会恢复到预填充的初始状态。需求是仅在应用首次安装时预填充数据,后续修改能永久保存。
相关代码
预填充数据库表结构
CREATE TABLE words (id INTEGER PRIMARY KEY, en TEXT, pl TEXT, speech TEXT)
实体类
@Entity(tableName = "words") data class MyEntity ( @PrimaryKey @ColumnInfo(name = "id")val id: Int, @ColumnInfo(name = "en") val englishWord: String?, @ColumnInfo(name = "pl") val polishWord: String?, @ColumnInfo(name = "speech") val speech: String? )
Hilt数据库提供器
@Provides @Singleton fun provide(@ApplicationContext context: Context) = Room.databaseBuilder( context, AppDatabase::class.java, MY_DATABASE) .createFromAsset("database/database.db") //.allowMainThreadQueries() .fallbackToDestructiveMigration() .build()
DAO方法
@Query("SELECT * FROM words") fun getAll(): LiveData<List<MyEntity>> @Update fun updateThat(ent: MyEntity)
解决方案
问题根源是createFromAsset()的默认行为:每次数据库不存在时都会从asset复制,若数据库版本变更且使用fallbackToDestructiveMigration(),会删除旧数据库再重新复制asset里的初始库,导致修改丢失。
方案1:自定义Room回调实现首次预填充
移除createFromAsset()调用,改为在数据库类中添加自定义回调,仅在数据库首次创建时执行预填充:
@Database(entities = [MyEntity::class], version = 1) abstract class AppDatabase : RoomDatabase() { abstract fun wordDao(): WordDao companion object { private val PREPOPULATE_CALLBACK = object : RoomDatabase.Callback() { override fun onCreate(db: SupportSQLiteDatabase) { super.onCreate(db) // 后台线程执行预填充操作 CoroutineScope(Dispatchers.IO).launch { // 可直接执行SQL插入预填充数据 db.execSQL("INSERT INTO words (id, en, pl, speech) VALUES (1, 'hello', 'cześć', 'noun')") // 批量数据可循环插入或使用事务优化 } } } fun getInstance(context: Context): AppDatabase { return Room.databaseBuilder(context, AppDatabase::class.java, MY_DATABASE) .addCallback(PREPOPULATE_CALLBACK) .fallbackToDestructiveMigration() .build() } } }
同步修改Hilt提供器:
@Provides @Singleton fun provideAppDatabase(@ApplicationContext context: Context): AppDatabase { return AppDatabase.getInstance(context) }
方案2:手动控制asset数据库复制时机
如果仍想保留从asset复制完整数据库的方式,可通过SharedPreferences标记判断是否首次启动,仅在首次启动时复制:
private fun copyDatabaseIfNotExists(context: Context) { val sharedPrefs = context.getSharedPreferences("app_prefs", Context.MODE_PRIVATE) val isFirstLaunch = sharedPrefs.getBoolean("first_launch", true) if (isFirstLaunch) { val dbPath = context.getDatabasePath(MY_DATABASE).absolutePath val dbFile = File(dbPath) if (!dbFile.exists()) { // 复制asset中的数据库到目标路径 context.assets.open("database/database.db").use { inputStream -> FileOutputStream(dbFile).use { outputStream -> inputStream.copyTo(outputStream) } } } // 标记首次启动已完成 sharedPrefs.edit().putBoolean("first_launch", false).apply() } }
在Application的onCreate()中先调用该方法,再初始化Room数据库,此时Room会直接使用已存在的数据库,不会再覆盖。
内容的提问来源于stack exchange,提问作者Rifat
相关产品推荐
相关产品推荐

