Как предварительно заполнить базу данных комнат, если ее экземпляр уже существует?

#android #kotlin #android-room

Вопрос:

Я хочу, чтобы мои пользователи могли сохранять базу данных комнат на Google диске, а затем загружать ее с него на другое устройство, первая часть уже реализована, но я борюсь с заполнением базы данных комнат новым файлом.

Это мой синглтон базы данных:

 companion object {
    @Volatile
    private var INSTANCE: AppRoomDataBase? = null
    fun getDatabase(context: Context): AppRoomDataBase {
        val tempInstance = INSTANCE
        if (tempInstance != null) {
            return tempInstance
        }
        synchronized(this) {
            val instance = Room.databaseBuilder(
                context.applicationContext,
                AppRoomDataBase::class.java,
                "product_database"
            ).build()
            INSTANCE = instance
            return instance
        }
    }
}
 

А затем, когда пользователь захочет загрузить базу данных с Google диска :

  private fun loadFileFromDrive() {
    val file = File(getExternalFilesDir(null), "product_database")
    mDriveServiceHelper.queryFiles().addOnSuccessListener { fileList ->
       val database = fileList.files.first()
        mDriveServiceHelper.downloadFile(file,database.id)?.addOnSuccessListener {
                prepopulateRoomDatabaseWithFile(file)
        }
    }
}


private fun prepopulateRoomDatabaseWithFile(file : File){
    Room.databaseBuilder(application,AppRoomDataBase::class.java,"product_database")
        .createFromFile(file)
        .build()
}
 

Но это не работает, есть только документация о том , как изначально создать базу данных из файла, но я не могу найти ничего о том, как заменить текущую базу данных номеров новой только тогда, когда это необходимо, возможно ли это?

Ответ №1:

Я попробовал решение MikeT, но оно не работает, потому что у меня есть много разных мест, где я использую базу данных КОМНАТ, и в каждом из них мне придется передать информацию о том, что есть новая база данных, которую следует использовать.

Лучшее решение для моего случая-просто переключить файлы базы данных, удалив предыдущий и переместив загруженный в каталог старого, как это :

 private fun swapDatabaseFiles(file: File){
    AppRoomDataBase.getDatabase(applicationContext).close()
    databaseFile.delete()
    copyFile(file.absolutePath,databaseFile.name,databaseFile.absolutePath)
    prepopulateRoomDatabaseWithFile(file)  // TO OPEN DATABASE
    val mainActivity = Intent(applicationContext,MainActivity::class.java)  // NOW RESTARTING ACTIVITY SO DATABASE IS REFRESHED
    mainActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    startActivity(mainActivity)
}



 private fun copyFile(inputPath: String, inputFileName: String, outputPath: String) {
    var inputStream: InputStream?
    var out: OutputStream?
    try {
        inputStream = FileInputStream(inputPath)
        out = FileOutputStream(outputPath   inputFileName)
        val buffer = ByteArray(1024)
        var read: Int
        while (inputStream.read(buffer).also { read = it } != -1) {
            out.write(buffer, 0, read)
        }
        inputStream.close()
        // write the output file (You have now copied the file)
        out.flush()
        out.close()
    } catch (fnfe1: FileNotFoundException) {
        fnfe1.message?.let { Log.e("tag fnfe1", it)
        }
    } catch (e: Exception) {
        Log.e("tag exception", e.message!!)
    }
}
 

функция базы данных подкачки внутри одного элемента базы данных :

 fun swapDatabase(context: Context,file : File){
            INSTANCE?.close()
            INSTANCE = null
            synchronized(this){
            val instance = Room.databaseBuilder(context.applicationContext,AppRoomDataBase::class.java,"product_database")
                .createFromFile(file)
                .build()
            INSTANCE = instance
        }
        }
 

Правка : Переработанный код и исправлено исключение ошибки. Теперь работает так, как и ожидалось.

Ответ №2:

Я считаю, что вам нужно закрыть исходную базу данных, а затем создать второй ЭКЗЕМПЛЯР настройки базы данных.

Ниже приведен пример (проверено только путем закрытия, а затем повторного открытия единой базы данных, см. Пример ниже).

 @Database(entities = [UserEntity::class],version = 1)
abstract class UserDatabase: RoomDatabase() {
    abstract fun getDao(): AllDao

    companion object {
        @Volatile
        private var instance: UserDatabase? = null
        private val DBNAME = "user.db"
        fun getInstance(context: Context, dbName: String = DBNAME): UserDatabase {
            if (instance == null) {
                synchronized(this) {
                    instance = Room.databaseBuilder(
                        context,
                        UserDatabase::class.java,
                        dbName
                    ).allowMainThreadQueries().build()
                }
                return instance as UserDatabase
            }
            return instance as UserDatabase
        }
        fun swapDatabase(context: Context, databaseFile: File): UserDatabase {
            // If we need to swap then (instance is not null) close the database if it is open
            if (instance != null) {
                if (instance!!.isOpen) {
                    instance!!.close() // should commit database
                    instance == null
                }
            }
            // instance should now be null and the original database closed
            val dbpath = context.getDatabasePath(DBNAME) // just showing how to get path where database should exist
            //copy database here from file
            return getInstance(context, dbpath.name) // note in theory a different file name (database file name) could be used 
        }
    }
}
 
  • Примечание для удобства и краткости .Был использован запрос allowMainThreadQueries.

Пример

В следующем примере используется вышесказанное (хотя и для замены ОДНОЙ и той же базы данных). :-

 class MainActivity : AppCompatActivity() {

    lateinit var db: UserDatabase
    lateinit var dao: AllDao
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        db = UserDatabase.getInstance(this)
        dao = db.getDao()
        dao.insertOneUser(UserEntity(0L,"UserFred","Fred@email.com","Fred","Bloggs","password"))
        for(u: UserEntity in dao.getAllUsers()) {
            Log.d("USERINFODB1","UserId = ${u.userid} UserName = ${u.userName}")
        }
        // SWAP THE DATABASE (in this case to the same database)
        db = UserDatabase.swapDatabase(this,this.getDatabasePath("xxx"))
        for(u: UserEntity in dao.getAllUsers()) {
            Log.d("USERINFODB2","UserId = ${u.userid} UserName = ${u.userName}")
        }
    }
}
 

При запуске журнал включает:-

 D/USERINFODB1: UserId = 0 UserName = UserFred
D/USERINFODB2: UserId = 0 UserName = UserFred
 

Комментарии:

1. Ваше решение было бы правильным, но не в моем случае, во-первых, если бы вы использовали другую базу данных, вы бы увидели, что она не работает, потому что dao не был обновлен, во-вторых, при таком подходе мне нужно было бы хранить ссылку на новую базу данных в каждом месте, где я ее использую.