Search code examples
sqliteandroid-sqliteandroid-room

Detect Room Migration Completion


Is there any way that we can detect that migration should happen if the user opens the application and also detect if the migrations completed for Room Database?


Solution

  • Is there any way that we can detect that migration should happen if the user opens the application?

    Yes, you can check the version number stored in the database against the coded version number before Room gets involved e.g. as soon as the App starts.

    e.g. you could have the following method and call it :-

    public static int getStoredVersion2(String databaseName, Context context) {
        int rv;
        File dbFile = new File(context.getDatabasePath(databaseName).getPath());
        if (dbFile.exists()) {
            SQLiteDatabase db = SQLiteDatabase.openDatabase(dbFile.getPath(), null, 0);
            rv = db.getVersion();
            db.close();
        } else {
            rv = -1;
        }
        return rv;
    }
    

    Or far less resource hungry, as it doesn't open the database, you could check the header (4 bytes at offset 60 see SQLite Database Format) e.g. :-

    public static int getStoredVersion(String databaseName, Context context) {
        int rv;
        byte[] readBuffer = new byte[4];
        InputStream i;
        try {
            i = new FileInputStream(context.getDatabasePath(databaseName));
            i.skip(60);
            i.read(readBuffer);
            rv = ByteBuffer.wrap(readBuffer).getInt();
            i.close();
        } catch (IOException e) {
            e.printStackTrace(); //?
            rv = -1;
        }
        return rv;
    }
    

    detect if the migrations completed for Room Database?

    This would be a little more difficult but if the migration didn't complete then it would fail. If you are not doing an AutoMigration then you could always introduce code to act as an indicator.

    If you mean did it do what you wanted then really that should be ironed out with testing before distributing the migration.

    What you could do is immediately after instantiating the database (i.e. getting an instance of the built database) is force open the database and then do whatever needs to be done.

    e.g. in an Activity you could have something like:-

    public class MainActivity extends AppCompatActivity {
    
    
    
        public static final int DBVersion = 2;
        TheDatabase db;
        AllDao dao;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
    
            Log.d("VERSION","Stored Database Version before Migration is (via file) " + TheDatabase.getStoredVersion("mylist.db",this));
            Log.d("VERSION","Stored Database Version before Migration is (via SQLiteDatabase)" + TheDatabase.getStoredVersion2("mylist.db",this));
            db = TheDatabase.getInstance(this);
            Log.d("VERSION","Stored Database Version after Migration is " + db.getOpenHelper().getWritableDatabase().getVersion());
            dao = db.getAllDao();
            for (Bookmark b: dao.getAll()) {
                Log.d("BOOKMARKINFO","ID = " + b.getId() + " PostTitle = " + b.getPostTitle() + " PostURL =" + b.getPostUrl());
            }
        }
    }
    

    The above, when run on a a new install, results in:-

    2021-07-01 06:55:58.229 W/System.err: java.io.FileNotFoundException: /data/user/0/a.a.so67958704javaroomconvertexistingdb/databases/mylist.db (No such file or directory)
    2021-07-01 06:55:58.229 W/System.err:     at java.io.FileInputStream.open0(Native Method)
    2021-07-01 06:55:58.229 W/System.err:     at java.io.FileInputStream.open(FileInputStream.java:231)
    2021-07-01 06:55:58.229 W/System.err:     at java.io.FileInputStream.<init>(FileInputStream.java:165)
    2021-07-01 06:55:58.229 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.TheDatabase.getStoredVersion(TheDatabase.java:58)
    2021-07-01 06:55:58.230 W/System.err:     at a.a.so67958704javaroomconvertexistingdb.MainActivity.onCreate(MainActivity.java:26)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.Activity.performCreate(Activity.java:7136)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.Activity.performCreate(Activity.java:7127)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
    2021-07-01 06:55:58.230 W/System.err:     at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
    2021-07-01 06:55:58.231 W/System.err:     at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
    2021-07-01 06:55:58.231 W/System.err:     at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
    2021-07-01 06:55:58.231 W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:106)
    2021-07-01 06:55:58.231 W/System.err:     at android.os.Looper.loop(Looper.java:193)
    2021-07-01 06:55:58.231 W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:6669)
    2021-07-01 06:55:58.231 W/System.err:     at java.lang.reflect.Method.invoke(Native Method)
    2021-07-01 06:55:58.231 W/System.err:     at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    2021-07-01 06:55:58.231 W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
    2021-07-01 06:55:58.231 D/VERSION: Stored Database Version before Migration is (via file) -1
    2021-07-01 06:55:58.232 D/VERSION: Stored Database Version before Migration is (via SQLiteDatabase)-1
    2021-07-01 06:55:58.277 D/VERSION: Stored Database Version after Migration is 2
    
    • Note the File not found is because the database doesn't exists. The exception is caught and output to the log (hence the ? in the line e.printStackTrace(); //?)
    • As can be seen the version is -1 before the Migration and 2 after.

    When run again (new APK without a DB change) then :-

    2021-07-01 07:08:20.947 D/VERSION: Stored Database Version before Migration is (via file) 2
    2021-07-01 07:08:20.956 D/VERSION: Stored Database Version before Migration is (via SQLiteDatabase)2
    2021-07-01 07:08:20.977 D/VERSION: Stored Database Version after Migration is 2
    
    • Note the file check method (getStoredVersion) does have the disadvantage/flaw that if the updated version number change hasn't been checkpointed (if using Write-Ahead Logging) then it may be incorrect/outdated, the getStoredVersion2 doesn't have this flaw.