Search code examples
androidreact-nativeandroid-roomandroid-room-prepackageddatabase

Prepopulate the Room database every time you restart Android


The room database is re-populated every time I re-run Android so any changes I made before quitting Android are not saved

I want this data to persist forever without Prepopulate the Room database once it's imported Here's the code I created to prepopulate

@Database(entities = {DeviceInfo.class, AddressInfo.class},version = 1)
public abstract class AppDataBase extends RoomDatabase {
    public abstract DeviceDao deviceDao();
    public abstract AddressDao addressDao();
    private static AppDataBase instance;
    public static synchronized AppDataBase getInstance(Context context){
        if(instance == null){
            instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class,"device.db")
                .createFromAsset("device.db")
                .fallbackToDestructiveMigration()
                .build();
        }

        return instance;
    }
}

Here's the update code

    @Update
    public void updateAddressInfo(AddressInfo addressInfo);

Solution

  • I believe that removing .fallbackToDestructiveMigration() will resolve your issue; an alternative is to ensure that the prepopulated database's user_version is set to the coded database version.

    • note only a coded version 1 has been tested. (see below)

    However, there shouldn't really be the need as the destruction should only be invoked if the database version differs from the coded version and the database's version should be set to the coded version.

    • i.e. this may well be a bug

    Some basic testing appears to highlight what may well be a bug. That is, if the prepopulated version is set to 0 (using PRAGMA user_version = 0;) then the copied database appears to be destroyed and copied again. If however, the prepopulated database's version (PRAGMA user_version = 1) then the database persists.


    Demonstration


    First some basic @Entity annotated classes were created as per:-

    @Entity
    class DeviceInfo {
        @PrimaryKey
        Long id=null;
        String name;
        public void setId(Long id) {
            this.id = id;
        }
        public Long getId() {
            return id;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }
    

    and :-

    @Entity
    class AddressInfo {
        @PrimaryKey
        Long id=null;
        String name;
        public void setId(Long id) {
            this.id = id;
        }
        public Long getId() {
            return id;
        }
        public void setName(String name) {
            this.name = name;
        }
        public String getName() {
            return name;
        }
    }
    

    Also some @Dao annotated interfaces:-

    @Dao
    interface DeviceDao {
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        long insert(DeviceInfo deviceInfo);
        @Query("SELECT * FROM deviceinfo")
        List<DeviceInfo> getAllDeviceInfoRows();
    }
    

    and :-

    @Dao
    interface AddressDao {
        @Insert(onConflict = OnConflictStrategy.IGNORE)
        long insert(AddressInfo addressInfo);
        @Query("SELECT * FROM addressinfo")
        List<AddressInfo> getAllAddressInfoRows();
        @Update
        public void updateAddressInfo(AddressInfo addressInfo);
    }
    

    Then a modified (to add a calback) @Database annotated AppDataBase class:-

    @Database(entities = {DeviceInfo.class, AddressInfo.class},version = 1)
    public abstract class AppDataBase extends RoomDatabase {
        public abstract DeviceDao deviceDao();
        public abstract AddressDao addressDao();
        private static AppDataBase instance;
        public static synchronized AppDataBase getInstance(Context context){
            if(instance == null){
                instance = Room.databaseBuilder(context.getApplicationContext(), AppDataBase.class,"device.db")
                        .createFromAsset("device.db")
                        .fallbackToDestructiveMigration()
                        .addCallback(new Callback() {
                            @Override
                            public void onCreate(@NonNull SupportSQLiteDatabase db) {
                                super.onCreate(db);
                                Log.d(MainActivity.TAG,"ONCREATE INVOKED.");
                            }
    
                            @Override
                            public void onDestructiveMigration(@NonNull SupportSQLiteDatabase db) {
                                super.onDestructiveMigration(db);
                                Log.d(MainActivity.TAG,"ONDESTRUCTIVEMIGRATION INVOKED DBVERSION=" + db.getVersion());
                            }
    
                            @Override
                            public void onOpen(@NonNull SupportSQLiteDatabase db) {
                                super.onOpen(db);
                                Log.d(MainActivity.TAG,"ONOPEN INVOKED");
                            }
                        })
                        .allowMainThreadQueries() /* added  for brevity */
                        .build();
            }
            return instance;
        }
    }
    
    • note that .allowMainThreadQueries added so the main thread could be utilised and thus reducing the code needed.

    Using Navicat the following SQL was used to create 2 database files one with user_vesrion as 0, other as 1 using:-

    DROP TABLE IF EXISTS `DeviceInfo`;
    DROP TABLE IF EXISTS `AddressInfo`; 
    CREATE TABLE IF NOT EXISTS `DeviceInfo` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`));
    CREATE TABLE IF NOT EXISTS `AddressInfo` (`id` INTEGER, `name` TEXT, PRIMARY KEY(`id`));
    INSERT OR IGNORE INTO `DeviceInfo` VALUES (100,'PPDEVICE001'),(null,'PPDEVICE002');
    INSERT OR IGNORE INTO `AddressInfo` VALUES (100,'PPADDRESS001'),(null,'PPADDRESS002');
    PRAGMA user_version = 0;
    PRAGMA user_version;
    
    • obviously PRAGMA user_version = 1, for the second file.

    The files were copied into the assets folder e.g. :- enter image description here

    • the device.db file being deleted and copied from the respective other file depending on the testing being undertaken.

    Finally some activity code to test:-

    public class MainActivity extends AppCompatActivity {
        public static final String TAG = "DBINFO";
    
        AppDataBase db;
        AddressDao addressDao;
        DeviceDao deviceDao;
    
        @Override
        protected void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.activity_main);
            db = AppDataBase.getInstance(this);
            deviceDao = db.deviceDao();
            addressDao = db.addressDao();
            getAll("_S1", db);
            for (AddressInfo ai: addressDao.getAllAddressInfoRows()) {
                ai.setName(ai.getName() + ai.getName());
                addressDao.updateAddressInfo(ai);
            }
            getAll("S2", db);
    
        }
    
        void getAll(String tag_suffix, AppDataBase adb) {
            logDBInfo(tag_suffix, adb);
            logDevices(tag_suffix);
            logAddresses(tag_suffix);
        }
        void logDBInfo(String tag_suffix,AppDataBase adb) {
            SupportSQLiteDatabase sdb = adb.getOpenHelper().getWritableDatabase();
            Log.d(TAG + tag_suffix, "DB VERSION is " + sdb.getVersion() + "DB PATH is " + sdb.getPath());
        }
    
        void logDevices(String tag_suffix) {
            for(DeviceInfo di: deviceDao.getAllDeviceInfoRows()) {
                Log.d(TAG + tag_suffix,"Device ID is " + di.getId() + " NAME is " + di.getName());
            }
        }
        void logAddresses(String tag_suffix) {
            for(AddressInfo ai: addressDao.getAllAddressInfoRows()) {
                Log.d(TAG + tag_suffix,"Address ID is " + ai.getId() + " NAME is " + ai.getName());
            }
        }
    }
    

    TESTING

    Test 1

    Initially device_userversion0.db was copied as device.db. The app uninstalled and then run (i.e. brand new install). Resulting in the log showing:-

    2023-12-20 15:58:01.204 D/DBINFO: ONCREATE INVOKED.
    2023-12-20 15:58:01.211 D/DBINFO: ONOPEN INVOKED
    2023-12-20 15:58:01.214 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 15:58:01.219 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 15:58:01.219 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 15:58:01.220 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001
    2023-12-20 15:58:01.220 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002
    2023-12-20 15:58:01.224 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 15:58:01.229 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 15:58:01.229 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 15:58:01.231 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
    2023-12-20 15:58:01.231 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
    
    • the result as expected, the database has been populated and then the 2 address rows have been updated (the name is now the original name repeated).

    Test 2

    The App is simply rerun, the output:-

    2023-12-20 16:01:11.520 D/DBINFO: ONCREATE INVOKED.
    2023-12-20 16:01:11.527 D/DBINFO: ONOPEN INVOKED
    2023-12-20 16:01:11.530 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:01:11.533 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:01:11.533 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:01:11.534 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001
    2023-12-20 16:01:11.534 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002
    2023-12-20 16:01:11.538 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:01:11.539 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:01:11.539 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:01:11.542 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
    2023-12-20 16:01:11.542 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
    
    • i.e. it is identical bar the actual run time and thus the updated data has not been retained (would have expected the address name to have been even longer).

    Test 3

    The line .fallbackToDestructiveMigration has been commented out and the App rerun. enter image description here

    This time the output is:-

    2023-12-20 16:05:31.133 D/DBINFO: ONOPEN INVOKED
    2023-12-20 16:05:31.136 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:05:31.140 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:05:31.140 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:05:31.142 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
    2023-12-20 16:05:31.142 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
    2023-12-20 16:05:31.164 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:05:31.166 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:05:31.167 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:05:31.169 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
    2023-12-20 16:05:31.169 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
    
    • i.e. the data has persisted rather than having been replaced (the address names are now 4 occurrences of the original name).

    Test 4 The App is uninstalled and run 3 times with .fallbackToDestructiveMigration commented out. This time the address rows are:-

    D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
    D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
    
    • i.e. 8 of the original names (as expected)

    **So at this stage not including .fallback.... get's around the issue (bug).

    Test5 The App is uninstalled and the device.db file is deleted and copied from device_userversion1.db and .fallbackToDestructiveMigration is reinstated as per:- enter image description here

    The output from the first run is:-

    2023-12-20 16:16:23.257 D/DBINFO: ONOPEN INVOKED
    2023-12-20 16:16:23.260 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:16:23.264 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:16:23.264 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:16:23.266 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001
    2023-12-20 16:16:23.266 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002
    2023-12-20 16:16:23.275 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:16:23.279 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:16:23.279 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:16:23.283 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
    2023-12-20 16:16:23.283 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
    
    • Note that ONCREATE is not called???, whilst previously it was on the first run

    Test 6 The App is rerun, the output:-

    2023-12-20 16:19:39.703 D/DBINFO: ONOPEN INVOKED
    2023-12-20 16:19:39.705 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:19:39.709 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:19:39.709 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:19:39.712 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001PPADDRESS001
    2023-12-20 16:19:39.712 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002PPADDRESS002
    2023-12-20 16:19:39.728 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:19:39.730 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:19:39.730 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:19:39.731 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
    2023-12-20 16:19:39.731 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
    
    • The data has been updated as expected, not recreated.

    Test 7 a third run:-

    2023-12-20 16:21:52.877 D/DBINFO: ONOPEN INVOKED
    2023-12-20 16:21:52.879 D/DBINFO_S1: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:21:52.883 D/DBINFO_S1: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:21:52.883 D/DBINFO_S1: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:21:52.886 D/DBINFO_S1: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
    2023-12-20 16:21:52.886 D/DBINFO_S1: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
    2023-12-20 16:21:52.901 D/DBINFOS2: DB VERSION is 1DB PATH is /data/user/0/a.a.so77689044javaroomalwaysprepopulates/databases/device.db
    2023-12-20 16:21:52.904 D/DBINFOS2: Device ID is 100 NAME is PPDEVICE001
    2023-12-20 16:21:52.904 D/DBINFOS2: Device ID is 101 NAME is PPDEVICE002
    2023-12-20 16:21:52.905 D/DBINFOS2: Address ID is 100 NAME is PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001PPADDRESS001
    2023-12-20 16:21:52.905 D/DBINFOS2: Address ID is 101 NAME is PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002PPADDRESS002
    
    • Again as expected. So setting the user_version to 1 results in the anticipated results. Hence why a bug is suspected (the version of the pre-populated database should not influence runs after the database has been populated).