I'm using Realm 3.4.0 and having one object that should be a singleton. The database is synced.
Here is a simplified version of the code: test if that object exist, add it if it doesn't exist. What is the correct way to do that? (copyToRealmOrUpdate shouldn't be needed or is there any other reason why the instance becomes null?)
@PrimaryKey
public long id = 1;
public static PlannerManager getInstance(Realm realm) {
PlannerManager ourInstance = null;
if (instanceLock == null)
instanceLock = new ReentrantLock();
try {
instanceLock.lock();
realm.refresh(); // Force getting all data from online database
ourInstance = realm.where(PlannerManager.class).findFirst();
if (ourInstance == null) { // The item doesn't exist
realm.beginTransaction();
ourInstance = realm.copyToRealm(new PlannerManager()); // Crashes sometimes with the error that an object with primary ID already exists
realm.commitTransaction();
}
} finally {
instanceLock.unlock();
}
return ourInstance;
}
Relevant part of the stacktrace
2:9.446 Primary key value already exists: 1 .
(/Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp:189) io.realm.exceptions.RealmPrimaryKeyConstraintException: Primary key value already exists: 1 .
(/Users/cm/Realm/realm-java/realm/realm-library/src/main/cpp/io_realm_internal_OsObject.cpp:189)
at io.realm.internal.OsObject.nativeCreateNewObjectWithLongPrimaryKey(Native Method)
at io.realm.internal.OsObject.createWithPrimaryKey(OsObject.java:198)
at io.realm.Realm.createObjectInternal(Realm.java:1052)
at io.realm.PlannerManagerRealmProxy.copy(PlannerManagerRealmProxy.java:1279)
at io.realm.PlannerManagerRealmProxy.copyOrUpdate(PlannerManagerRealmProxy.java:1268)
at io.realm.DefaultRealmModuleMediator.copyOrUpdate(DefaultRealmModuleMediator.java:438)
at io.realm.Realm.copyOrUpdate(Realm.java:1660)
at io.realm.Realm.copyToRealm(Realm.java:1072)
at com.myapp.internal.PlannerManager.getInstance(PlannerManager.java:857)
Thanks!
Your logic is actually slightly wrong. By doing the query outside the transaction, the background sync thread might put data into Realm between you do the query and begin the transaction. Transactions will always move the Realm to the latest version, so calling refresh()
is also not needed. Your logic should be something like:
realm.beginTransaction();
ourInstance = realm.where(PlannerManager.class).findFirst();
if (ourInstance == null) {
ourInstance = realm.createObject(PlannerManager.class, 1);
realm.commitTransaction();
} else {
realm.cancelTransaction();
}
Note, that using realm.copyToRealm()
will cause changes from other devices to be overridden, so for initial data like this, it is safer to use createObject
as changes to individual fields will then merge correctly. Using copyToRealm()
is the same as actually setting all fields to the initial value.
E.g if you have two devices A and B that are both offline:
PlannerManager
.PlannerManager
.copyToRealm
is the equivalent of setting all fields manually.Using Realm.createObject()
uses a special "default" instruction for all fields, that automatically loses to any explicit set like the one used when using normal Java setters (and which copyToRealm
uses).