#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 не был обновлен, во-вторых, при таком подходе мне нужно было бы хранить ссылку на новую базу данных в каждом месте, где я ее использую.