Room cannot verify the data integrity in Android - android

MainActivity class
public class MainActivity extends BaseActivity {
private AppDatabase db;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// Database creation
db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "Medimap-db").build();
// Profile profile=new Profile("rajitha","12-2-345","male","no 345","test med","test emergency","testurl");
// db.profileDao().insert(profile);
// Toast.makeText(getApplicationContext(),"success",Toast.LENGTH_SHORT).show();
new DatabaseAsync().execute();
}
private class DatabaseAsync extends AsyncTask<Void, Void, Void> {
#Override
protected void onPreExecute() {
super.onPreExecute();
//Perform pre-adding operation here.
}
#Override
protected Void doInBackground(Void... voids) {
//Let's add some dummy data to the database.
Profile profile = new Profile("rajitha", "12-2-345", "male", "no 345", "test med", "test emergency", "testurl");
db.profileDao().insert(profile);
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
Toast.makeText(getApplicationContext(), "success", Toast.LENGTH_SHORT).show();
//To after addition operation here.
}
}
}
AppDatabase class
#Database(entities = {Profile.class, Medicine.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public abstract ProfileDao profileDao();
public abstract MedicineDao medicineDaoDao();
}
ProfileDao
#Dao
public interface ProfileDao {
#Query("SELECT * FROM profile")
List<Profile> getAll();
// #Query("SELECT * FROM user WHERE uid IN (:userIds)")
// List<Profile> loadAllByIds(int[] userIds);
// #Query("SELECT * FROM user WHERE first_name LIKE :first AND "
// + "last_name LIKE :last LIMIT 1")
// User findByName(String first, String last);
#Insert
void insertAll(Profile... profiles);
#Insert
void insert(Profile profile);
#Delete
void delete(Profile profile);
}
Here I got an error after the first run of the app. It seems like the app is trying to create DATABASE once again, but there already is an existing one so they suggested to change the version code. My requirement is that I only need to insert a new data set. How can I achieve that? Thanks in advance. Here is the logcat error:
Caused by: java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.

Android 6.0+
For those of you who come across this now a days, simply uninstalling might not help and it will drive you nuts, so before you have to figure it out, let me share the issue: Google introduced the "autobackup" functionality in
Android 6.0, which brings back a DB when you reinstall. (https://developer.android.com/guide/topics/data/autobackup.html)
You can simply add...
<application ...
android:allowBackup="false">
</app>
...to disable this functionality and you are good to go (now you can simply uninstall to delete the database).

If you're only developing the application at the moment, it means that you are not on Production and uninstalling the app from device and install it again will work as required.

Increase the version:
#Database(entities = {EmployeeEntry.class}, version = 2, exportSchema = false)
Took from
link

If you don't care about migrations when update entities:
Increase database version
Add fallbackToDestructiveMigration() method at the time of building database, e.g:
Room.databaseBuilder(appContext, AppDatabase.class, "appdb.db")
.fallbackToDestructiveMigration()
.build();
Hope this help someone ;)

There are 3 ways to fix this data integrity exception.
1) If you don't want to increase your Database version and you can bear data loss
Just clear your App data from App settings after installing new build in device and launch App again, it would work without data integrity exception
2) If you can increase your Database version but still you can bear data loss
If you want to make it seamless( no crash due to schema update), you can simply increase Database version (let say from 1 to 2) and mention Default migration strategy in Database Builder fallbackToDestructiveMigration(). Then on installing this App, your previous DB data would clear up and you wouldn't get this data integrity exception.
3) If you can increase your Database version and also want to save your previous data
You need to increase your Database version(let say from 1 to 2) but also need to implement proper migrations logic (based on your lower version and higher version) in your App. Then on installing new version, you will not get this data integrity exception.
For migrations of Room you can read out this nice article written for migrations
For official documentation of Room migrations go to link

In my case, reinstalling didn't help and also I couldn't set android:allowBackup:true because one of the libraries I had used required it. So I went to Settings > Apps > My app, and cleared data and memory for that app. Problem gone;)

Step 1: Install new build
Step 2: Clear all app data from Settings.
Step 3: Open app ( will work fine now )

You can also use adb shell pm uninstall com.package.app in your terminal to fully uninstall the app. After that, install it again and the error will disappear.

Related

Android Hilt - update references to a given dependency

I am working on refactoring an android application (I'm not the original author) which uses a pre-created sqlite database file received from the backend. It is done like this because my client's use case needs a local database in which one of the tables can have 1 million rows in some cases. It is a stock-taking app for a rugged device which needs to work offline which means that the device needs to store the entire database of all the various products that can be found for the given warehouse so that the workers can see a product's information after scanning it's barcode. Every day at the start of the work on a new project, the pre-created database gets acquired from the backend and is used for the remainder of the project for the rest of the day.
I use Room for the database and also use Hilt. Normally everything works fine. The problem arises when/if the client uses a functionality in which the app can re-download the pre-created database from the backend which means that the entire database file Room uses gets rewritten. To avoid having references to a database that no longer exists, I close the database by calling my closeDatabase() method which then later gets recreated. The database class is the following (I shortened it and changed names due to NDA reasons):
#Database(
entities = [
ItemTable::class
],
exportSchema = false,
version = 1
)
abstract class ProjectDatabase : RoomDatabase() {
abstract fun roomItemDao(): ItemDao
companion object {
#Volatile
private var INSTANCE: ProjectDatabase? = null
fun getDatabase(): ProjectDatabase {
return INSTANCE ?: synchronized(this) {
val instance = Room.databaseBuilder(
MyApplication.appContext,
ProjectDatabase::class.java,
getDbNameWithPath()
).createFromAsset(getDbNameWithPath())
.build()
INSTANCE = instance
instance
}
}
fun closeDatabase() {
INSTANCE?.close()
INSTANCE = null
}
private fun getDbNameWithPath(): String {
return MyApplication.appContext.filesDir.toString() +
File.separator + Constants.PROJECT_DATABASE_NAME
}
}
}
I also use a Hilt module for the database like this:
#Module
#InstallIn(SingletonComponent::class)
class ProjectDatabaseModule {
#Provides
fun provideProjectDatabase(): ProjectDatabase {
return ProjectDatabase.getDatabase()
}
#Provides
fun provideItemDao(
projectDatabase: ProjectDatabase
): ItemDao {
return projectDatabase.roomItemDao()
}
}
My problem is that when I set the INSTANCE to null, then the next call to getDatabase() creates a new instance, but all the references previously created by Hilt for the various classes still reference the old instance.
If I don't call INSTANCE = null then the database doesn't get reopened. If I don't close the database, then Room goes insane due to having its entire underlying database completely changed. Previously I always called getDatabase().xyz which worked but was kinda ugly, thus I started to use Hilt for it.
Is there a solution for this via Hilt? I'm afraid I'll have to go back to my old solution. Even if I change my scope to something else, the already existing classes will use the old reference.
Basically what I wish for is a call to ProjectDatabase.getDatabase.roomItemDao() every time I call a method of ItemDao.
I decided that the best solution in this case is to either ask the user to restart your app or do it programmatically according to your given use case.
Thanks to #MikeT for his reassuring comment.

How to force Android Virtual Device to forget database version of my application

I have an application with prefilled database (I use Room, Kotlin). Database class:
#Database(
entities = [FurnitureModel::class, ImageModel::class],
version = Database.VERSION,
exportSchema = true
)
abstract class Database : RoomDatabase() {
abstract val furnitureDao: FurnitureDao
abstract val imageDao: ImageDao
companion object {
const val NAME = "application-data"
const val VERSION = 3
#Volatile
private var instance: Database? = null
fun getInstance(context: Context): Database {
synchronized(this) {
var inst = instance
if (inst == null) {
inst = Room.databaseBuilder(
context.applicationContext,
Database::class.java,
"$NAME-local.db"
)
.fallbackToDestructiveMigration()
.createFromAsset("$NAME.db")
.build()
instance = inst
}
return inst
}
}
}
}
It worked fine. Once I decided to update some data inside database. No stucture changes, just adding and changing some data. For versions, I changed constant VERSION from 3 to 4. When I run the application, I've got empty database. As I understand, it is possible to get empty database with fallbackToDestructiveMigration in few situations. I could made some mistake somewhere in data. To get more details I decided to run application without preinstalled previous version. Clean install, if I can say like this. To do it, I wiped data for Virtual device in AVD Manager, removed build directories in application and app directories and run 'Invalidate cache/restart...' in Android Studio (I know that there is no need to do it). But the result is always the same: trying to install my application I always get error
Caused by: java.lang.IllegalStateException: A migration from 3 to 4 was required but not found. Please provide the necessary Migration path via RoomDatabase.Builder.addMigration(Migration ...) or allow for destructive migrations via one of the RoomDatabase.Builder.fallbackToDestructiveMigration* methods.
As I understand, the error must not be shown when you install the application for the first time. But it is shown. I tried to create new virtual device and run app there, but the error is still there.
What should I do to forse Android Studio to forget that the application was installed before?
Thank you.
UPD1: I don't need to keep any data, actually I want to delete all data from previous versions of application. But after wiping data it still works as if I didn't wipe it. This is the problem.
RESULT: I found that the problem was in prefilled database. Unfortunatedly I can't find that is exactly wrong with database. I disabled creating database from assets, run application so that Room created bew empty database, copied the database from emulator and compared each table with my database. No any difference. So to solve the problem I just moved all data in each table to the same table in new empty database (using DataGrid). After running the application new database the problem disapeared.

Android: Database from past implementations being mysteriously created despite the code no longer existing

I am in the process of creating an Android app and I have been bouncing back and forth between different implementations of my database. And for whatever reason, my app is creating databases that are from my past implementations, not the current one, in my data folder even though I have seemingly deleted all the code from my past implementations. Here is some context on how I've changed my database implementation.
I first started out creating my database using SQLiteOpenHelper but quickly abandoned this implementation when I discovered the Room Persistence Library.
I created a database using Room for 3 entities: SubTask, MainTask, and a junction table/entity to relate those 2 entities.
I realized that creating a junction table was not the best implementation since a SubTask can only belong to one MainTask. So I scrapped the junction entity and added an attribute to SubTask to relate with MainTask. This is my current implementation and I plan to stick to it.
This is the current code I have for my database. I have decided to populate it to see if it would translate:
#Database(entities = {SubTask.class, MainTask.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {
public static final String DATABASE_NAME = "task_db";
private static AppDatabase instance;
public static synchronized AppDatabase getInstance(final Context context) {
if (instance == null) {
instance = Room.databaseBuilder(
context.getApplicationContext(),
AppDatabase.class,
DATABASE_NAME
).fallbackToDestructiveMigration().addCallback(roomCallback).build();
}
return instance;
}
public abstract SubTaskDao getSubTaskDao();
public abstract MainTaskDao getMainTaskDao();
//TESTING TO POPULATE DATABASE
private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
#Override
public void onCreate(#NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
new PopulateDbAsyncTask(instance).execute();
}
};
private static class PopulateDbAsyncTask extends AsyncTask<Void, Void, Void> {
private MainTaskDao mainTaskDao;
private SubTaskDao subTaskDao;
private PopulateDbAsyncTask(AppDatabase db) {
mainTaskDao = db.getMainTaskDao();
subTaskDao = db.getSubTaskDao();
}
#Override
protected Void doInBackground(Void... voids) {
Calendar date1 = Calendar.getInstance();
date1.set(2019, 2, 10);
mainTaskDao.insertMainTasks(new MainTask("School Assignments", 0xFF0000, date1));
Calendar date2 = Calendar.getInstance();
date2.set(2019, 2, 8);
subTaskDao.insertSubTasks(new SubTask("Read database book", date2, 1));
Calendar date3 = Calendar.getInstance();
date3.set(2019, 2, 6);
subTaskDao.insertSubTasks(new SubTask("Read OS book", date3, 1));
return null;
}
}
}
When I build and open my app for the first time, it crashes and I get the following in the log:
2019-03-05 19:53:36.241 20085-20106/com.myname.divideandconquer E/AndroidRuntime: FATAL EXCEPTION: pool-1-thread-1
Process: com.myname.divideandconquer, PID: 20085
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
at android.arch.persistence.room.RoomOpenHelper.checkIdentity(RoomOpenHelper.java:135)
at android.arch.persistence.room.RoomOpenHelper.onOpen(RoomOpenHelper.java:115)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen(FrameworkSQLiteOpenHelper.java:151)
at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked(SQLiteOpenHelper.java:409)
at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase(SQLiteOpenHelper.java:298)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase(FrameworkSQLiteOpenHelper.java:96)
at android.arch.persistence.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase(FrameworkSQLiteOpenHelper.java:54)
at android.arch.persistence.room.RoomDatabase.query(RoomDatabase.java:233)
at com.johnsorhannus.divideandconquer.room.SubTaskDao_Impl$4.compute(SubTaskDao_Impl.java:159)
at com.johnsorhannus.divideandconquer.room.SubTaskDao_Impl$4.compute(SubTaskDao_Impl.java:145)
at android.arch.lifecycle.ComputableLiveData$2.run(ComputableLiveData.java:100)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
at java.lang.Thread.run(Thread.java:764)
When I look in the Device File Explorer, I see the following for my app:
Image 1. task_db is the database being created with Room. I opened this database with a third-party app and this file contains tables for 3 entities, not 2, so this appears to be from implementation (2). taskManager is the database that I created using SQLiteOpenHelper (implementation 1), and it is populated with data that I had hardcoded into the database. I am baffled as to why this database exists. I have deleted all my code from this implementation. I have even searched my entire project for taskManager to see if I ever gave a database this name anywhere in my code. Nothing!
When I go into the app settings and clear storage and I run the app again on Android Studio, I get no error in the log and I get the following in my Device File Explorer: Image 2. task_db is completely empty. No tables at all, so I am not exactly sure whether this is from implementation (2) or (3). This is also surprising because I expected the database to be populated with the values that I hardcoded.
I have no idea why these databases are being created even though I have deleted code from my past two implementations. There no longer contains any trace of code from SQLiteOpenHelper and I have deleted the Dao and Entity for the junction table. Any clue as to what is going on?
Look at the error. it is showing you the reason of error.
java.lang.IllegalStateException: Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.
So you have changed some schema in you code. So simply change the version of database from 1 to 2.
Database(entities = {SubTask.class, MainTask.class}, version = 2)
you have to uninstall the app (or delete the files from the internal storage), else the Room schema of the previous version will persist (just alike it would persist, when having created these files with the SQLiteOpenHelper) and then the new schema will be compared to the old schema. increasing the version number only makes sense for apps released already, when intending to create a migration.

Android Realm Migration version number based on what?

I'm doing my first Realm migration and started thinking about the version number. On what is this version number based?
Because if it is based on what is on your phone, how do I handle it if a new person installs the app and gets a migration? Because it will also update the fields which where already set because of a fresh install.
Christian from Realm here. The migration API is still in a very experimental state and kinda ugly, so right now the version number always start with 0, and the only way for changing that is through a migration.
This means that if you want a fresh install with a different version other than 0, you will have to do something like:
// Pseudo code
public class RealmHelper() {
private static SharedPreferences prefs;
public static Realm getInstance() {
if (!prefs.getBoolean("versionSet", false)) {
String path = new File(context.getFilesDir(), Realm.DEFAULT_REALM_NAME).getAbsolutePath();
Realm.migrateRealmAtPath(path, new RealmMigration() {
#Override
public long execute(Realm realm, long version) {
return 42; // Set version numbers
}
})
prefs.edit().putBoolean("versionSet", true).apply();
}
return Realm.getInstance();
}
}
This is going to be a lot better soon though: https://github.com/realm/realm-java/pull/929
Realm migrations in 0.84.2 are changed quite a bit (see Christian's hint on the new API), the key points on making a realm (0.84.2) migration work for me were understanding that:
The schemaVersion is always 0 when your app has a realm db without
specifying the schemaVersion. Which is true in most cases since you
probably start using the schemaVersion in the configuration once you
need migrations & are already running a live release of your app.
The schemaVersion is automatically stored and when a fresh install of your app occurs and you are already on schemaVersion 3, realm
automatically checks if there are exceptions, if not it sets the
schemaVersion to 3 so your migrations aren't run when not needed.
This also meens you don't have to store anything anymore in
SharedPreferences.
In the migration you have to set all values of new columns when the type is not nullable, current version of realm, ...
Empty Strings can be inserted but only when setting convertColumnToNullable on the column

How to test database migration in Android?

I have an Android app with a database with version 1.
Now I like to change the structure of the database and write all the migration code etc.
The problem I have is how to test this.
I need to repeatedly have the old version of the app with its old DB so I can test the update process to the new app with the new DB several times.
I thought to simplify things I make a copy of my project and just rename it new - leaving the manifest and everything unchanged!!!!
The idea is to run/install the old version of the app with the old DB structure using Eclipse so I can create the start situation of my update.
Now to simulate the user update to the new app version I install it just over the old version using Eclipse again) - but even without having changed the db I get an error that the DB already exists ???
I am confused (I have only changed the project name, not the manifest) I would have expected that I can just install the new version over the old version and hereby test the user update of the app.
But that does not work.
How would I best do this? (without having the old and the new database code in the very same project. since if I have the same project I have two different data base structures in there and need to build in a switch how it should start up, ie as old or new. I find it cleaner to just write the new version of the app with the new database structure in there)
Room is one of the Android Architecture Components for database stuff.
Room provides a testing Maven artifact to assist with this testing process.
First you need to export db scheme by set the room.schemaLocation annotation processor property in your build.gradle file
android {
...
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = ["room.schemaLocation":
"$projectDir/schemas".toString()]
}
}
}
}
#RunWith(AndroidJUnit4.class)
public class MigrationTest {
private static final String TEST_DB = "migration-test";
#Rule
public MigrationTestHelper helper;
public MigrationTest() {
helper = new MigrationTestHelper(InstrumentationRegistry.getInstrumentation(),
MigrationDb.class.getCanonicalName(),
new FrameworkSQLiteOpenHelperFactory());
}
#Test
public void migrate1To2() throws IOException {
SupportSQLiteDatabase db = helper.createDatabase(TEST_DB, 1);
// db has schema version 1. insert some data using SQL queries.
// You cannot use DAO classes because they expect the latest schema.
db.execSQL(...);
// Prepare for the next version.
db.close();
// Re-open the database with version 2 and provide
// MIGRATION_1_2 as the migration process.
db = helper.runMigrationsAndValidate(TEST_DB, 2, true, MIGRATION_1_2);
// MigrationTestHelper automatically verifies the schema changes,
// but you need to validate that the data was migrated properly.
}
}
You can read an official document for more info
You can create the main file 'create.sql' and the migrations files called '1.sql, 2.sql,.., n.sql'.
Store all these files in assets for example.
If the user has no database, apply create.sql
If the user needs a database upgrade from A to Z, then apply a+1.sql, a+2.sql, …, z.sql.
You need implement applyUpgrades method that you'll use on upgrade version of DB and for migration tests. It's look like:
fun Context.applyUpgrades(db: SQLiteDatabase, from: Int, to: Int): Unit =
(from+1..to).forEach { index ->
val migrationSql = getMigrationFromAssets(index)
db.execSQL(migrationSql)
}
Your migration tests will look like:
#Test
fun upgrade10to11() { // when you upgrade from 10 to 11
val db = SQLiteDatabase.create(null)
// apply all previous migrations
applyUpgrades(db, from = INITIAL, to = 10)
val valuesBefore = ..
// our new migration
applyUpgrades(db, from = 10, to = 11)
val valuesAfter = ..
// assert that valuesBefore and valuesAfter are correct
}
Also you can check that you did not forget write test for new migration:
#Test
public void hasAllNecessaryTests() {
assertEquals(DB_VERSION, 11);
}
Trello bless
As you only need the data and structures to test migration, you could make backups of the old databases and use them in your tests.
So for each test:
install app, put data in, make backup (once)
restore backup, run test (often)
That way you only have to make the backup once and can restore them on every test.
If your tests are automated, you could even restore the backups automatically as well.
For SQLite, making backups is as easy as copying the database file, but pretty much every database has a way to make and restore backups.
Your updated app seems to try to create the database (not the data inside) again, but with the database of the old app still being around that won't work.
This may be caused by the app not noticing it was installed before.

Categories

Resources