I have migrated room database from version 1 to 2 but I was facing a problem. I solved it after seeing this post : java.lang.IllegalStateException: Migration didn't properly handle:. The app was opening but old data is getting lost. Please suggest me a way to keep old data even after migration.
CourseModel.java
package com.gtappdevelopers.gfgroomdatabase;
import android.graphics.Bitmap;
import android.icu.text.UFormat;
import androidx.room.ColumnInfo;
import androidx.room.Entity;
import androidx.room.PrimaryKey;
import java.text.SimpleDateFormat;
import java.util.Date;
//below line is for setting table name.
@Entity(tableName = "course_table")
public class CourseModal {
//below line is to auto increment id for each course.
@PrimaryKey(autoGenerate = true)
private int EnrollmentStatus;
//variable for our id.
private int id;
private String firstName;
private String middleName;
private String lastName;
private String DOB;
private String gender;
private String address;
private String designation;
private String email;
private String phoneNumber;
@ColumnInfo(typeAffinity = ColumnInfo.BLOB)
byte[] image;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getDOB() {
return DOB;
}
public void setDOB(String DOB) {
this.DOB = DOB;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
public String getDesignation() {
return designation;
}
public void setDesignation(String designation) {
this.designation = designation;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhoneNumber() {
return phoneNumber;
}
public void setPhoneNumber(String phoneNumber) {
this.phoneNumber = phoneNumber;
}
public byte[] getImage() {
return image;
}
public void setImage(byte[] image) {
this.image = image;
}
public int getEnrollmentStatus() {
return EnrollmentStatus;
}
public void setEnrollmentStatus(int enrollmentStatus) {
EnrollmentStatus = enrollmentStatus;
}
//below line we are creating constructor class.
//inside constructor class we are not passing our id because it is incrementing automatically
public CourseModal(String firstName, String middleName, String lastName,String DOB,String gender,String address,String designation,String email,String phoneNumber,Bitmap image) { //,Bitmap image
this.firstName = firstName;
this.middleName = middleName;
this.lastName = lastName;
this.DOB = DOB;
this.gender = gender;
this.address = address;
this.designation = designation;
this.email = email;
this.phoneNumber = phoneNumber;
this.image = DataConverter.convertImageToByteArray(image);
}
public CourseModal(){}
}
CourseDatabase.java
package com.gtappdevelopers.gfgroomdatabase;
import android.content.Context;
import android.os.AsyncTask;
import androidx.annotation.NonNull;
import androidx.room.Database;
import androidx.room.Room;
import androidx.room.RoomDatabase;
import androidx.room.migration.Migration;
import androidx.sqlite.db.SupportSQLiteDatabase;
//adding annotation for our database entities and db version.
@Database(entities = {CourseModal.class}, version = 3)
//@TypeConverters((ImageBitmapString.class))
public abstract class CourseDatabase extends RoomDatabase {
//below line is to create instance for our database class.
private static CourseDatabase instance;
//below line is to create abstract variable for dao.
public abstract Dao Dao();
//upgrade database (add new changes in db via update)
static Migration MIGRATION_1_2 = new Migration(1,2) {
@Override
public void migrate(@NonNull SupportSQLiteDatabase database) {
database.execSQL("CREATE TABLE course_table_new1(" +
" id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL," +
" firstName TEXT," +
" middleName TEXT," +
" lastName TEXT," +
" DOB TEXT," +
" gender TEXT," +
" address TEXT," +
" designation TEXT," +
" email TEXT," +
" phoneNumber TEXT," +
" EnrollmentStatus INTEGER NOT NULL DEFAULT 0," +
// " dateInserted TEXT," +
" image BLOB);");
//copy old data into new table
database.execSQL("INSERT INTO course_table_new1(id,firstName,middlename,lastname,DOB,gender,address,designation,email,phoneNumber,image) SELECT id,firstName,middlename,lastname,DOB,gender,address,designation,email,phoneNumber,image FROM course_table");
//remove old table
database.execSQL("DROP TABLE course_table");
//rename the table
database.execSQL("ALTER TABLE course_table_new1 RENAME TO course_table");
}
};
//on below line we are getting instance for our database.
public static synchronized CourseDatabase getInstance(Context context) {
//below line is to check if the instance is null or not.
if (instance == null) {
//if the instance is null we are creating a new instance
instance =
//for creating a instance for our database we are creating a database builder and passing our database class with our database name.
Room.databaseBuilder(context.getApplicationContext(),
CourseDatabase.class, "course_database")
//below line is use to add fall back to destructive migration to our database.
.fallbackToDestructiveMigration()
//below line is to add callback to our database.
.addCallback(roomCallback)
//below line is to build our database.
.addMigrations(MIGRATION_1_2)
//below line is to upgrade our database.
.build();
}
//after creating an instance we are returning our instance
return instance;
}
//below line is to create a callback for our room database.
private static RoomDatabase.Callback roomCallback = new RoomDatabase.Callback() {
@Override
public void onCreate(@NonNull SupportSQLiteDatabase db) {
super.onCreate(db);
//this method is called when database is created and below line is to populate our data.
new PopulateDbAsyncTask(instance).execute();
}
};
//we are creating an async task class to perform task in background.
private static class PopulateDbAsyncTask extends AsyncTask<Void, Void, Void> {
PopulateDbAsyncTask(CourseDatabase instance) {
Dao dao = instance.Dao();
}
@Override
protected Void doInBackground(Void... voids) {
return null;
}
}
}
I was expecting a working app with old data present in the database and new column added in the database.
It looks as though your issue is that you
thus that the fallbackToDestructiveMigration
has been actioned.
i.e. you have
@Database(entities = {CourseModal.class}, version = 3)
, and.fallbackToDestructiveMigration()
, and ONLY.addMigrations(MIGRATION_1_2)
The fallbackToDestructiveMigration
will then drop the course_table and then create it and it will be empty.
You need to
onCreate
and also onOpen
and onDestructiveMigration
) so that you can see what is/isn't running. That would allow you to then determine what has or hasn't been done/run.Furthermore, if you fix the above then you have issues with the CREATE TABLE course_table_new1( ....
as that does not correspond to the @Entity
That is that:-
@Entity(tableName = "course_table")
public class CourseModal {
//below line is to auto increment id for each course.
@PrimaryKey(autoGenerate = true)
private int EnrollmentStatus;
//variable for our id.
private int id;
....
Defines the table with the EnrollmentStatus column as the Primary Key, whilst:-
id INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
defines the id column as the Primary Key.
If the Migration were to run then it would result in a Room unable to handle Migration exception as the actual table found would not be the expected schema as per the @Entity annotated definition of the table.
I believe that you need to use:-
@Entity(tableName = "course_table")
public class CourseModal {
//below line is to auto increment id for each course.
@PrimaryKey(autoGenerate = true)
//variable for our id.
private int id;
private int EnrollmentStatus;
....