lmdic
lmdic

Reputation: 89

Android, Realm: After add annotation @PrimaryKey get error: Following changes cannot be made in additive-only schema mode:

Realm 3.5.0. I get json from external system. And I need to save this json to Realm.

Here json:

{
    "id": 1,
    "address": "Address 1",
    "contactsIds": [10, 20, 30, 40],
    "name": "Person 1"
}

In my Android application:

So I create POJO for Person:

public class Person extends RealmObject {
    @PrimaryKey
    private int id;
    @Required
    private String name;
    private String address;
    private RealmList<RealmInt> contactsIds = new RealmList<>();
}

My POJO for RealmInt:

public class RealmInt extends RealmObject {
    private int value;

    public int getValue() {
        return value;
    }

    public void setValue(int value) {
        this.value = value;
    }
}

My custom json deserializer for RealmInt (by GSON):

public class RealmIntAdapterJsonDeserializer implements JsonDeserializer<RealmInt> {

    @Override
    public RealmInt deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {
        RealmInt realmInt = new RealmInt();
        realmInt.setValue(json.getAsJsonPrimitive().getAsInt());
        return realmInt;
    }
}

Snippet that save json to Realm:

    public void fromJsonToRealm(final String someJson) {
        Realm realm = Realm.getDefaultInstance();

        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
               Person person = JsonUtil.gson.fromJson(someJson, Person.class);
               realm.copyToRealmOrUpdate(person);

            }
        });
}

Result of call method "fromJsonToRealm" is:

OK.

Table "Person" and "RealmInt" must be share with many Android devices. So I use SyncConfiguration for this. Here snippet:

    private void setRealmDefaultConfiguration(SyncUser syncUser) { 
         SyncConfiguration config = new SyncConfiguration.Builder(syncUser, REALM_URL).build();
        Realm.setDefaultConfiguration(config); 
    } 

So now I want to update this person. I get the new json (for same Person) with updated data:

{
    "id": 1,
    "address": "Address new",
    "contactsIds": [10, 20],
    "name": "Person new"
}

I again start method "fromJsonToRealm" and as result it success update ONE record in table "Person", BUT add new 2 records in table "RealmInt". As result in table "RealmInt" I has 6 records.

But I want to update(not insert) records in table "RealmInt" when I update same object "Person".

So then I add annotation @PrimaryKey to field "value" in object "RealmInt".

But when I start again method "fromJsonToRealm" I get error:

E/REALM_JNI( 9018): jni: ThrowingException 8, The following changes cannot be made in additive-only schema mode:
E/REALM_JNI( 9018): - Primary Key for class 'RealmInt' has been added., .
E/REALM_JNI( 9018): Exception has been thrown: The following changes cannot be made in additive-only schema mode:
E/REALM_JNI( 9018): - Primary Key for class 'RealmInt' has been added.

So the question is: How I can update exist record in table "Person" when get updated json for SAME object Person?

Upvotes: 2

Views: 1991

Answers (1)

EpicPandaForce
EpicPandaForce

Reputation: 81588

Adding/removing @PrimaryKey is considered a "destructive" schema operation, so it is not allowed in sync mode.

With your current schema setup, you ought to delete the existing RealmInts from the Realm before you copy the new Person in.

public void fromJsonToRealm(final String someJson) {
    try(Realm realm = Realm.getDefaultInstance()) {
        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
               Person person = JsonUtil.gson.fromJson(someJson, Person.class);
               long personId = person.getId();
               Person managedPerson = realm.where(Person.class).equalTo("id", personId).findFirst();
               if(managedPerson != null) {
                   managedPerson.getContactIds().deleteAllFromRealm();
               }
               realm.copyToRealmOrUpdate(person);
            }
        });
    }

Another option would be to implement some smarter merge logic

public void fromJsonToRealm(final String someJson) {
    try(Realm realm = Realm.getDefaultInstance()) {
        realm.executeTransactionAsync(new Realm.Transaction() {
            @Override
            public void execute(Realm realm) {
               Person person = JsonUtil.gson.fromJson(someJson, Person.class);
               long personId = person.getId();
               Person managedPerson = realm.where(Person.class).equalTo("id", personId).findFirst();
               if(managedPerson != null) {
                   boolean wasFound = false;
                   for(RealmInt existingInt : managedPerson.getContactIds()) {
                       for(RealmInt newInt : person.getContactIds()) {
                           if(existingInt.getValue() == newInt.getValue()) {
                               wasFound = true;
                               break;
                           }
                       }
                       if(!wasFound) {
                           existingInt.deleteFromRealm();
                       }
                   }
                   Iterator<RealmInt> newIntIterator = person.getContactIds().iterator();
                   while(newIntIterator.hasNext()) {
                       RealmInt newInt = newIntIterator.next();
                       boolean wasFound = false;
                       for(RealmInt existingInt : managedPerson.getContactIds()) {
                           if(existingInt.getValue() == newInt.getValue()) {
                               wasFound = true;
                               break;
                           }
                       }
                       if(!wasFound) {
                           newIntIterator.remove();
                       }
                   }
               }
               realm.copyToRealmOrUpdate(person);
            }
        });
    }

P.S. I don't use RealmList<RealmInt>s, I prefer to just save them as a comma separated string. Realm Core 3.0.0 will make this use-case much easier though.

Upvotes: 1

Related Questions