androidandroid-roomdatabase-migration

While adding a new column in database the old data gets lost. How to keep old data even after database migration version in Android Room Database?


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.


Solution

  • It looks as though your issue is that you

    1. do not have a 2-3 Migration and/or a 1-3 Migration AND
    2. that the version number has been increased to 3

    thus that the fallbackToDestructiveMigration has been actioned.

    i.e. you have

    The fallbackToDestructiveMigration will then drop the course_table and then create it and it will be empty.

    You need to

    1. revert to the database that has data
    2. implement a Migration to cover the Migration from the reverted database version to the new version
    3. correct the issue below
    4. rerun

    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;
        ....