Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to properly close and reopen Room Database

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.

like image 633
Andre Rocha Avatar asked Apr 06 '18 09:04

Andre Rocha


1 Answers

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 ?

like image 109
Andre Rocha Avatar answered Sep 30 '22 23:09

Andre Rocha