Right way of doing Realm Migration Android
Asked Answered
N

2

29

We use Realm for our app. Our app has been beta released. Now I want to add a field to one of our realm objects. So I got to write RealmMigration and I wrote one too. The Question here is how to apply this Realm migration to my app. I use Realm.getInstance() get the realm instance whenever I want to something. Remember, Realm.getInstance() is being used in the entire app everytime, I want to access Realm database.

So, I am bit Queried on how to apply this migration? Any leads can be helpful. Thanks.

My RealmMigration is as follows.

public class RealmMigrationClass implements RealmMigration {
    @Override
    public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
        if(oldVersion == 0) {
            RealmSchema sessionSchema = realm.getSchema();

            if(oldVersion == 0) {
                RealmObjectSchema sessionObjSchema = sessionSchema.get("Session");
                sessionObjSchema.addField("isSessionRecordingUploading", boolean.class, FieldAttribute.REQUIRED)
                        .transform(new RealmObjectSchema.Function() {
                            @Override
                            public void apply(DynamicRealmObject obj) {
                                obj.set("isSessionRecordingUploading", false);
                            }
                        });


                sessionObjSchema.setNullable("isSessionRecordingUploading",false);
                oldVersion++;
            }

        }
    }

}

public class Session extends RealmObject {

    @PrimaryKey
    private String id;
    @Required
    private Date date;
    private double latitude;
    private double longitude;
    private String location;
    private String note;
    private String appVersion;
    private String appType;
    private String deviceModel;
    private HeartRecording heart;
    private TemperatureRecording temperature;
    private LungsRecording lungs;
    @NotNull
    private boolean isSessionRecordingUploading;
    private boolean sessionInfoUploaded;
    private boolean lungsRecordingUploaded;
    private boolean heartRecordingUploaded;

}

Removed Getter and Setters from RealmObject to cut short the Question. The exception occurred when I try to reinstall the app without uninstalling the previous one. Please advice.

Nahshun answered 7/3, 2016 at 5:28 Comment(0)
T
39

It is described here: https://realm.io/docs/java/latest/#migrations

But in essence, you just bump the schema version and set the configuration like this:

RealmConfiguration config = new RealmConfiguration.Builder(context)
    .schemaVersion(2) // Must be bumped when the schema changes
    .migration(new MyMigration()) // Migration to run
    .build();

Realm.setDefaultConfiguration(config);

// This will automatically trigger the migration if needed
Realm realm = Realm.getDefaultInstance();
Tom answered 7/3, 2016 at 8:29 Comment(6)
Thanks Christian. If my understanding is correct, model will have newly created field and all the Realm.getInstance(this) would be Realm.getInstance(config<created like above>)Nahshun
Yes, that is correct. You should use Realm.getDefaultInstance() or Realm.getInstance(RealmConfiguration) instead of Realm.getInstance(Context).Tom
Today I tried, I am getting "io.realm.exceptions.RealmMigrationNeededException: Field 'isSessionRecordingUploading' does support null values in the existing Realm file. Use corresponding boxed type for field 'isSessionRecordingUploading' or migrate using io.realm.internal.Table.convertColumnToNotNullable()" . I modified RealmMigration Class little bit and added notNull annotation to the field in my Realm Object. But still I could not overcome the issue. I updated the Q.Nahshun
notNull is not an annotion that Realm understands. From the error it looks like the boolean is currently optional. Which means you either have to do private Boolean isSessionRecordingUploading in the java class or perform the following migration realm.getSchema().get("Session").setNullable("isSessionRecordingUploading", false)Tom
I am using the getDefaultInstance method in all the classes where I need it. But the init and setDefaultConfiguration methods in my Application class. I hope this is the right way.Cusco
This is the right way - the RealmConfiguration can be saved as a default configuration. Setting a default configuration in your custom Application class makes it available in the rest of your code, according to realm docs.Chamberlin
D
1

This is a helper class i have made to import a .realm database to my application. In my case i just need to read the db created from an iOS application in an Android application

public class RealmImporter {
private Activity activity;
private String dbaFullName = "db.realm";
private int rawRealmResource = R.raw.dbRealmResource;


public RealmImporter (Activity activity) {
    this.activity = activity;
    importData();
}


private RealmConfiguration getConfiguration() {
    RealmConfiguration config = new RealmConfiguration.Builder()
            .name(dbaFullName)
            .modules(new MyRealmModule())
            .migration(new RealmMigration() {
                @Override
                public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {

                }
            })
            .build();

    return config;
}

public Realm getInstance() {
    Realm realm = Realm.getInstance(getConfiguration());

    return realm;
}

private void importData() {
    copyBundledRealmFile(activity.getResources().openRawResource(rawRealmResource), dbaFullName);

    Realm.init(activity);
}

private String copyBundledRealmFile(InputStream inputStream, String outFileName) {
    try {
        File file = new File(activity.getFilesDir(), outFileName);
        if (file.exists()) {
            return file.getAbsolutePath();
        }
        FileOutputStream outputStream = new FileOutputStream(file);
        byte[] buf = new byte[1024];
        int bytesRead;

        while ((bytesRead = inputStream.read(buf)) > 0) {
            outputStream.write(buf, 0, bytesRead);
        }

        outputStream.close();
        return file.getAbsolutePath();
    } catch (Exception e) {
        e.printStackTrace();
    }
    return null;
}


// Create the module
@RealmModule(classes = { Artigo.class, Comparativo.class, Indice.class, ItemDeArtigo.class})
public static class MyRealmModule{
}
}

If you have more than one realm configuration (if you need to import more than one .realm file) you need to create a RealmModule class and use the .modules() option on the RealmConfiguration.Builder .

This is needed because by default realm try to check if your .realm has tables for all your models that extend RealmObject, and if you have multiple .realm, each one of them will have some of your classes (but not all of them).

Another usefull information is that for swift to android migrations. All String properties must be declared with @Required anototation (if they have a default value).

If realm is giving you a strange Exception like "this class was not found in this schema", try to re run you app, usually it gives a different error which can hint to the real problem although sometimes it just gives a random error.

Deposit answered 22/10, 2016 at 19:25 Comment(4)
is RealmConfiguration.assetFile() too mainstream? Also, there are no "random errors", you should probably disable Instant Run.Arrestment
Hi, im just trying to help other users. Feel free to edit my code with your sugestions. And althrought i disabled Instant Run, i still have run into random errors like "this table/class wont exist" even when im sure that the file and the table exists, and it end up that the error was because of the "module".Deposit
I'm just saying that Realm already has assetFile(), so you don't have to manually write the copying :PArrestment
Thank you, but it would be more usefull if you edit my code using your approach, which is indeed better.Deposit

© 2022 - 2024 — McMap. All rights reserved.