3

I am using Android Room in an app. I have a pre-build database in assets which I need to use. Each time i update the version i need to replace old db with new DB. For this I have copied the database with conventional file IO.

private fun copyDatabaseFile(context: Context) {
    val dbPath = context.getDatabasePath(DB_NAME)
    if (dbPath.exists() && AppPrefs.getInstance().dataBaseVersion== DATABASE_VERSION) {
        return
    }
    if(dbPath.delete())Log.e("datacopy", "file deleted")

    dbPath.parentFile.mkdirs()
    try {
        val inputStream = context.assets.open("databases/no_database.db")
        val output = FileOutputStream(dbPath)
        val buffer = ByteArray(8192)
        var length =0
        while ({length = inputStream.read(buffer, 0, 8192); length}() > 0) {
            Log.e("datacopy", "Write")
            output.write(buffer, 0, length)
        }
        output.flush()
        output.close()
        inputStream.close()
        AppPrefs.getInstance().dataBaseVersion= DATABASE_VERSION
    } catch (e: IOException) {
        e.printStackTrace()
    }
}
}

Here is the getter for Database instance :-

 internal fun getDatabase(context: Context): MyDatabase? {
        if (INSTANCE == null) {
            synchronized(MyDatabase::class.java) {
                if (INSTANCE == null) {
                    copyDatabaseFile(context)
                    INSTANCE = Room.databaseBuilder(
                        context.applicationContext,
                        MyDatabase::class.java, DB_NAME)
                        .addMigrations(MyDatabase.MIGRATION_1_2)
                        .build()
                }
            }

        }
        return INSTANCE
    }

Migration is Empty for now cause i need to wipe out old one a copy new one. So i think migration is not required for this .

@JvmField
    val MIGRATION_1_2: Migration = object : Migration(1, 2) {
        override fun migrate(database: SupportSQLiteDatabase) {


        }
    }

First time all working fine. When I upgrade the version of database I am deleting the old file copy new file, but Room is returning the old data. I am not sure what's the problem.

I even tried some older answer which suggests:

  1. How to use Room Persistence Library with pre-populated database? library-with-pre-populated-database

  2. https://github.com/humazed/RoomAsset

Also tried some other stuff from Git, but none of it worked. What could be the issue? My need is replace old database with new one each time database version is upgrade.

ADM
  • 20,406
  • 11
  • 52
  • 83
  • What are you doing in the MIGRATION_1_2 class? – user Feb 21 '19 at 15:37
  • NOthing .. Cause i need wipe out the previous one and copy a new one . – ADM Feb 21 '19 at 15:38
  • If you see the old data it seems that you don't replace the database. Do you see the logs you set up in the copyDatabaseFile() method? Any exceptions in Logcat? – user Feb 21 '19 at 15:54
  • Yep .. I debugged the whole copying process . Old file is getting deleted and new file is getting copied .. This is why i am in dilemma how the hell room is still pointing to old one . I used Sqlite with Asset db many times and i am putting the same logic here in room but its does not seems to work .. – ADM Feb 21 '19 at 15:56
  • If Room is using `SQLiteOpenHelper.getReadableDatabase()`, as this suggests https://stackoverflow.com/a/50383879/19506, then note that the copy is not done until the `getReadableDatabase()` call - NOT when the db file is first specified in `new`. I had a similar problem copying/updating a db after app install until I realised this and forced a `getReadableDatabase().close()` after `new` and before the rest of the app accessed it. – mm2001 Feb 14 '20 at 00:57

1 Answers1

0

The safest and most complete method:

Make fun closeRemoveCopy().

private fun closeRemoveCopy(outputFile: File){
    // 1st: Close DB
    RoomHelper.getDatabase(applicationContext)?.close()
    // 2nd: Delete DB file
    outputFile.delete()
    // 3rd: Copy New DB file
    copyDatabase(this)
    // 4th: TO OPEN DB
    val handler = Handler(Looper.getMainLooper())
    handler.postDelayed({
        helper = Room.databaseBuilder(this@MainActivity, RoomHelper::class.java, "room_database.db")
            .build()
    }, 0)
    // 5th: Reopen App. If you don't reopen the app, it will be closed.
    val mainActivity = Intent(applicationContext,MainActivity::class.java)  // NOW RESTARTING ACTIVITY SO DATABASE IS REFRESHED
    mainActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
    startActivity(mainActivity)
}

And make fun copyDatabase().

private fun copyDatabase(context: Context) {
    val dbPath: File = context.getDatabasePath("room_database.db")
    // If the database already exists, return
    if (dbPath.exists()) {
        Log.d("Show Result", "db Path Exists. $dbPath")    
        return
    }      
    dbPath.parentFile?.mkdirs()
    // Try to copy DB file
    try {
        val inputStream: InputStream = context.assets.open("databases/room_database.db")
        val output: OutputStream = FileOutputStream(dbPath)
        val buffer = ByteArray(8192)
        var length: Int
        while (inputStream.read(buffer, 0, 8192).also { length = it } > 0) {
            output.write(buffer, 0, length)
        }
        output.flush()
        output.close()
        inputStream.close()
    } catch (e: IOException) {
        Log.d("Show Result", "Failed", e)           
        e.printStackTrace()
    }
}

And Use.

override fun onCreate(savedInstanceState: Bundle?) {   
        super.onCreate(savedInstanceState)

        // management db file version
        val preferences = getSharedPreferences("prefGetData", MODE_PRIVATE)
        val editor = preferences.edit()
        val getCheckData = preferences?.getString("getData", "")
        val currentDbFileVer : String = "1.1"   // data version

        if (getCheckData != currentDbFileVer) {
            Toast.makeText(this,"data: $currentDbFileVer",Toast.LENGTH_SHORT).show()
            lifecycleScope.launch(Dispatchers.IO) {
               // DO SOMETHING for time works
               withContext(Dispatchers.IO) {
                 val dbPath: File = applicationContext.getDatabasePath("room_database.db")
                 closeRemoveCopy(dbPath)  
               }
            }
        }
        // write db file version by SharedPreferences
        editor.putString("getData", currentDbFileVer).apply()
}

■ Notice: Whatever function you create and use, the important thing is the order of closing DB, deleting DB-file, and copying the new DB-file.

Young Lee
  • 1
  • 1