Hello I have 2 apps that rely on making and restoring the backups of the app's databases just by copying the databases files in and out the sdcard and am having a hard time figuring out how to reopen the Room Database singleton after closing it to make the databases' copies.
Building the database:
@Database(version = 15, exportSchema = true, entities = [list of entities])
abstract class AppDatabase : RoomDatabase() {
//list of DAOs
companion object {
@Volatile private var INSTANCE: AppDatabase? = null
fun getInstance(context: Context): AppDatabase =
INSTANCE ?: synchronized(this) {
INSTANCE ?: buildDatabase(context).also {
INSTANCE = it
}
}
private fun buildDatabase(context: Context) =
Room.databaseBuilder(
context.applicationContext,
AppDatabase::class.java,
"Fazendao.sqlitedb"
)
.addMigrations(Migration1315)
.build()
}
}
Closing the database:
fun closeDatabase() {
if(db.isOpen) {
db.openHelper.close()
}
}
Making the database file copy (inside a ViewModel):
fun exportaBkpObservable(nome: String, auto: String, storage: File, database: File) {
disposable.clear()
setFlagsNull()
flagSubject.onNext(false)
disposable.add(
Observable.fromCallable {
repo.recordBkpName(nome)
}
.subscribeOn(Schedulers.io())
.flatMap {
return@flatMap try {
//closing the database
repo.closeDatabase()
Observable.just(
database.copyTo(File(storage, auto), true)
)
.flatMap {
val myDb = SQLiteDatabase.openOrCreateDatabase(it, null)
val ok = myDb.isDatabaseIntegrityOk
if(myDb.isOpen) myDb.close()
if(ok) {
Observable.just(ok)
} else {
Observable.error<Throwable>(Throwable("CORRUPTED DATABASE"))
}
}
} catch (t: Throwable) {
Observable.error<Throwable>(t)
}
}
.subscribe(
{},
{
errorFlag = "exportDB: " + it.message
errorSubject.onNext("exportDB: " + it.message)
},
{
//trying to reopen database
repo.openDatabase()
trueFlag = true
flagSubject.onNext(true)
}
)
)
}
The repo is the repository where the AppDatabase is injected, and in its turn is injected in the ViewModelFactory.
Injection:
object MainInjection {
private fun providesIORepo(context: Context): IORepo {
return IORepo(AppDatabase.getInstance(context))
}
fun provideIOViewModelFactory(context: Context): IOViewModelFactory {
val data = providesIORepo(context)
return IOViewModelFactory(data)
}
}
And in the AppCompatActivity onCreate:
val modelFactory = MainInjection.provideIOViewModelFactory(this)
viewModel = ViewModelProviders.of(this, modelFactory).get(IOViewModel::class.java)
Reopening the database:
fun openDatabase() {
if(!db.isOpen){
db.openHelper.writableDatabase
}
}
Now the error messages:
Trying to reopen the database:
E/ROOM: Invalidation tracker is initialized twice :/.
And consequently the crash when I try to access it from another function:
Cannot perform this operation because the connection pool has been closed.
Sometimes after closing the database I have also:
E/ROOM: Cannot run invalidation tracker. Is the db closed?
In this post Incrementally migrate from SQLite to Room the author opens and closes the database for each access to it, so I don't understand why my implementation is not working.
So where am I wrong ? Is there a way of deactivating the InvalidationTracker too ?
Should I use the following code to close the database and clear the Room instance every time I have to copy the database file. Is it safe ?:
fun destroyInstance() {
if (INSTANCE?.isOpen == true) {
INSTANCE?.close()
}
INSTANCE = null
}
Thanks for the attention.
Ok I started using to close the database the following code:
fun destroyInstance() {
if (INSTANCE?.isOpen == true) {
INSTANCE?.close()
}
INSTANCE = null
}
and implemented the import database as follows
fun importaBkpObservable(origin: File, database: File) {
disposable.clear()
setFlagsNull()
flagSubject.onNext(false)
disposable.add(
Observable.fromCallable {
try {
repo.closeDatabase()
val myDb = SQLiteDatabase.openOrCreateDatabase(origin, null)
val ok = myDb.isDatabaseIntegrityOk
if (myDb.isOpen) myDb.close()
if(ok) {
origin.copyTo(database, true)
} else {
"CORRUPTED DATABASE"
}
} catch (t: Throwable) {
t.message
}
}
.subscribeOn(Schedulers.io())
.subscribe(
{
if(it != null) {
if(it is String) {
errorFlag = "exportDB: $it"
errorSubject.onNext("exportDB: $it")
} else {
trueFlag = true
flagSubject.onNext(true)
}
} else {
errorFlag = "exportDB: GENERIC"
errorSubject.onNext("exportDB: GENERIC")
}
},
{
errorFlag = "exportDB: ${it.message}"
errorSubject.onNext("exportDB: ${it.message}")
}
)
)
}
I used to navigate from my main activity to the import/export activity through startActivityForResult() but now have changed to just startActivity() finishing my main activity after this call. When the import/export is finished I call my main activity back with startActivity() and then finish my import/export activity.
This way my main activity ViewModel is instantiated again with the new AppDatabase instance, and all is working fine.
I have looked into the Android Profiler and the memory usage is ranging between 90 MB to 130 MB after several imports and exports, same as before when I wasn't closing the database, so I think I am not running into some sort of memory leak or accumulating Room Databases instances.
What else should I check ?
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With