Hi,
I have problem with migration in my application. All time logcat gives me "io.realm.exceptions.RealmMigrationNeededException: Field count is more than expected - expected 10 but was 11 after migration".
My old model:
@PrimaryKey
private int sid;
private String title;
private String noSpecialCharsTitle;
private String lang;
private String img;
private String icon;
private boolean owned;
private boolean favorite;
private int favouritePosition = Integer.MAX_VALUE;
My new model:
@PrimaryKey
private int sid;
private String title;
private String noSpecialCharsTitle;
private String lang;
private String img;
private String icon;
private boolean owned;
private boolean favorite;
private int favouritePosition = Integer.MAX_VALUE;
private int stationPosition = Integer.MAX_VALUE;
Migration:
Realm.init(ApplicationClass.getAppContext());
RealmConfiguration realmConfig = new RealmConfiguration.Builder()
.schemaVersion(SCHEMA_VERSION)
.migration((realm, oldVersion, newVersion) -> {
if(oldVersion == 0) {
final RealmObjectSchema station = realm.getSchema().get("Station");
station.addField("stationPosition", int.class);
oldVersion++;
}
})
.build();
Realm.setDefaultConfiguration(realmConfig);
What's wrong with this migration? Why app always throws RealmMigrationNeededException?
You can try
RealmConfiguration realmConfig = new RealmConfiguration.Builder()
.schemaVersion(SCHEMA_VERSION)
.migration((realm, oldVersion, newVersion) -> {
if(oldVersion == 0) {
final RealmObjectSchema station = realm.getSchema().get("Station");
if(!station.hasField("stationPosition") {
station.addField("stationPosition", int.class);
}
oldVersion++;
}
})
.build();
Realm.setDefaultConfiguration(realmConfig);
Still same crash.
RealmMigrationNeededException: Field count is more than expected - expected 10 but was 11.
Out of curiousity, what happens if you don't specify a migration at all?
Without any migration:
RealmMigrationNeededException: Field 'stationPosition' not found for type Station
Okay, this is odd. What version of Realm are you using?
I'm using Realm 2.1.1.
I've checked version 2.2.0, but nothing changed.
Hmm........ do you call this in Application.onCreate()? How many times does Application.onCreate() run when you start the app? (Add log to it)
Although if you have only 1 process, then I am running out of ideas, and will need the help of an actual Realm collaborator.
Is this reproducible? Do you by chance have a sample project for it?
You can also try to print the content of station.getFields() that will list all fields known by Realm and should give you an idea why there might be an extra field.
The model classes outlined above has 9 and 10 fields but your Realm file has 11 fields. If you are a MacOS user, try to copy the file from the device and open it on your computer using the Realm Browser.
@kneth
Fortunately i'm MacOS user. I opened database in Realm Browser. I found 11th field - 'position'.
In migration i don't have this field.
I changed stationPosition to position in model and now everything works fine, but it's a little odd.

@jakubjodelka I guess the field was added when you debugging the migration? with the field in the schema, your old version apk should crash with a migration needed exception.
But it is good that you found the problem. I am closing this issue now. Thanks!
This is a strange issue, because I haven't run into this problem while testing just now.
public class Station extends RealmObject {
@PrimaryKey
private String id;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
public class MainActivity
extends AppCompatActivity {
private static final String TAG = "MainActivity";
Realm realm;
RealmConfiguration realmConfiguration;
@Override
protected void onCreate(Bundle savedInstanceState) {
Realm.init(this);
this.realmConfiguration = new RealmConfiguration.Builder().build();
Realm.setDefaultConfiguration(realmConfiguration);
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
realm = Realm.getDefaultInstance();
}
@Override
protected void onDestroy() {
if(realm != null) {
realm.close();
}
super.onDestroy();
}
}
Station classpublic class Station extends RealmObject {
@PrimaryKey
private String id;
private int stationPosition;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public int getStationPosition() {
return stationPosition;
}
public void setStationPosition(int stationPosition) {
this.stationPosition = stationPosition;
}
}
Ran the app, crashed with RealmMigrationNeededException as expected
Then I changed schema version to 1 and added migration
this.realmConfiguration = new RealmConfiguration.Builder().schemaVersion(1).migration(new RealmMigration() {
@Override
public void migrate(DynamicRealm realm, long oldVersion, long newVersion) {
Log.d(TAG, "Old version: [" + oldVersion + "], new version: [" + newVersion + "]");
RealmSchema realmSchema = realm.getSchema();
for(long version = oldVersion; version < newVersion; version++) {
if(version == 0) { // to 1
RealmObjectSchema stationSchema = realmSchema.get("Station");
stationSchema.addField("stationPosition", int.class);
}
}
}
}).build();
And it worked without problem.
So for schema version 2 I removed stationPosition, and for version 3 added field stationSchema.addField("position", int.class); while the class itself has stationPosition, so I wrote
@PrimaryKey
private String id;
private int stationPosition;`
And the migration to version 3 was
} else if(version == 2) { // to 3
RealmObjectSchema stationSchema = realmSchema.get("Station");
stationSchema.addField("position", int.class);
}
And then StationRealmProxy crashed with
Caused by: io.realm.exceptions.RealmMigrationNeededException: Field 'stationPosition' not found for type Station
The schema version however seems to have been increased to 3 so the migration itself succeeded because it passed the "field count validation", except the field it is looking for doesn't actually exist.
So I'll have to add a new migration from 3 to 4:
} else if(version == 3) { // to 4
RealmObjectSchema stationSchema = realmSchema.get("Station");
stationSchema.addField("stationPosition", int.class);
}
And now it says:
Caused by: io.realm.exceptions.RealmMigrationNeededException: Field count is more than expected - expected 2 but was 3
So I added field but now the schema version is 4, so I have to kill one:
} else if(version == 4) { // to 5
RealmObjectSchema stationSchema = realmSchema.get("Station");
stationSchema.removeField("position");
}
So basically the only thing that could have happened is that is that the OP added a field named position in a migration, the field count validation didn't throw an exception so it went through, then he tried to rename it, but he should have incremented schemaVersion and removed position and added stationPosition.
@cmelchior however this means that "migration needed exception" is not thrown if the field count matches, but the field names don't match. Is this ok?
Yes, same problem. 2.2.1
Never had problems with migrations. But now:
I had 9 fileds in class. I add one more: "boolean cheated"
if (oldVersion == 6) {
schema.get(Geolocation.class.getSimpleName()).addField("cheated", Boolean.class);
oldVersion++;
}
But after migration i got: RealmMigrationNeededException: Field count is more than expected - expected 9 but was 10
I printed all fileds and there are 10 fields, but for some reason expected 9...
Did you print the fields using https://realm.io/docs/java/latest/api/io/realm/RealmObjectSchema.html#getFieldNames-- ?
Yes
StreamSupport.stream(schema.get(Geolocation.class.getSimpleName()).getFieldNames()).forEach(field -> { Log.e("XXX", field); });
Okay, so what is the RealmObject that you're supposed to have, and what are the printed fields?
Realm class:
public class Geolocation extends RealmObject {
private Long id;
private Coordinates coordinates;
private Date date;
private Float accuracy;
private String type;
private Boolean locCorrect;
private Boolean createdGeolocation;
private Boolean ignoreGeoCheck;
private Long posId;
private Boolean cheated;
Migration:
if (oldVersion == 6) {
schema.get(Geolocation.class.getSimpleName()).addField("cheated", Boolean.class);
oldVersion++;
}
StreamSupport.stream(schema.get(Geolocation.class.getSimpleName()).getFieldNames()).forEach(field -> Log.e("XXX", field));
Result:
W/Realm聽Migration: Start migration: 6 to 7
E/XXX: id
E/XXX: coordinates
E/XXX: date
E/XXX: accuracy
E/XXX: type
E/XXX: locCorrect
E/XXX: createdGeolocation
E/XXX: ignoreGeoCheck
E/XXX: posId
E/XXX: cheated
And after crash:
Caused by: io.realm.exceptions.RealmMigrationNeededException: Field count is more than expected - expected 9 but was 10
at io.realm.GeolocationRealmProxy.validateTable(GeolocationRealmProxy.java:564)
at io.realm.DefaultRealmModuleMediator.validateTable(DefaultRealmModuleMediator.java:353)
at io.realm.Realm.initializeRealm(Realm.java:342)
at io.realm.Realm.createAndValidate(Realm.java:299)
at io.realm.Realm.createInstance(Realm.java:278)
Just for test, I tried increment bd version and try add field again:
Got error as expected (show that previous migration finished successfully)
Caused by: java.lang.IllegalArgumentException: Field already exists in 'Geolocation': cheated
Temporarily reverted to 2.1.1, no problems here.
If you have a Realm file, model classes and migration steps which can reproduce the error, we will appreciate if you can share it all.
Thanks, this bug gone since 2.2.2 version. 馃憤
The best way to get rid of this problem, while you have are creating your Application, is to uninstall the application and reinstall the application again because it will remove the older version of Realm Database from your testing device.
You may as well just set deleteIfMigrationNeeded() on the RealmConfiguration then
@Zhuinden Yes, that will work, but what if we have instantiates the Realm with a default RealmConfiguration.
Realm.init(context);
Realm realm = Realm.getDefaultInstance();
In above case, we would not be able to use this method deleteIfMigrationNeeded().
Realm.init(context);
Realm.setDefaultConfiguration(new RealmConfiguration.Builder()
.deleteIfMigrationNeeded()
.build());
Realm realm = Realm.getDefaultInstance();`
class BedevApplication : Application() {
override fun onCreate() {
super.onCreate()
Realm.init(this)
val config = RealmConfiguration.Builder()
.name("user.db")
.build()
Realm.setDefaultConfiguration(config)
}
}
add .deleteRealmIfMigrationNeeded()
class BedevApplication : Application() {
override fun onCreate() {
super.onCreate()
Realm.init(this)
val config = RealmConfiguration.Builder()
.name("user.db")
.deleteRealmIfMigrationNeeded()
.build()
Realm.setDefaultConfiguration(config)
}
}
Well, yeah, if you can afford to delete the data in Realm (although it makes sense to have a second copy on a backend)