Goal
Query an "enemy" object without the use of a transaction.
Expected Results
Query and return an "enemy" object from realm.
Actual Results
During the query, the constructor is called again on the stored "enemy" object and since it is live, the app crashes asking for any data changed to be done in a transaction.
Steps & Code to Reproduce
I am a self-taught and inexperienced programmer so my code will be very unorganized and almost unreadable but here it goes.
Going off of my app's files, these are the ones to take note of:
The migration file. I recently added in Enemy to the schema. No errors appeared during migration but I thought it might be useful to go step by step in what I did.
https://github.com/pookieofdoom/DamageCalculator/blob/master/app/src/main/java/com/padassist/Util/Migration.java
> if(oldVersion == 5){
schema.create("Enemy")
.addField("enemyId", long.class, FieldAttribute.PRIMARY_KEY)
.addField("enemyName", String.class, FieldAttribute.INDEXED)
.addField("monsterIdPicture", long.class)
.addField("targetDef", int.class)
.addField("beforeDefenseBreak", int.class)
.addField("damageThreshold", int.class)
.addField("damageImmunity", int.class)
.addField("reductionValue", int.class)
.addField("targetHp", long.class)
.addField("currentHp", long.class)
.addField("beforeGravityHP", long.class)
.addField("gravityPercent", double.class)
.addRealmListField("targetElement", schema.get("RealmElement"))
.addRealmListField("reduction", schema.get("RealmElement"))
.addRealmListField("absorb", schema.get("RealmElement"))
.addRealmListField("gravityList", schema.get("RealmInt"))
.addRealmListField("types", schema.get("RealmInt"))
.addField("hasAbsorb", boolean.class)
.addField("hasReduction", boolean.class)
.addField("hasDamageThreshold", boolean.class)
.addField("isDamaged", boolean.class)
.addField("hasDamageImmunity", boolean.class);
}
The enemy Realm Object. Its pretty basic. It shares no relationships to any other Realm objects I have saved. I have it working with Parceler too. My other realm objects work with Parceler with no errors but the Enemy object is the only one that behaves this way. I used the parcel property converters in other objects so they work fine. It all parcels nicely so I thought Realm might've been the cause of the crash.
https://github.com/pookieofdoom/DamageCalculator/blob/master/app/src/main/java/com/padassist/Data/Enemy.java
public class Enemy extends RealmObject {
@PrimaryKey
private long enemyId;
@Index
private String enemyName;
private long monsterIdPicture;
private int targetDef;
private int beforeDefenseBreak;
private int damageThreshold;
private int damageImmunity;
private int reductionValue;
private long targetHp;
private long currentHp;
private long beforeGravityHP;
private double gravityPercent;
private RealmList<RealmElement> targetElement;
private RealmList<RealmElement> reduction;
private RealmList<RealmElement> absorb;
private RealmList<RealmInt> gravityList;
private RealmList<RealmInt> types;
private boolean hasAbsorb = false;
private boolean hasReduction;
private boolean hasDamageThreshold;
private boolean isDamaged;
private boolean hasDamageImmunity;
//default is DKali from Arena 3
public Enemy() {
enemyId = 0;
enemyName = "Blazing Goddess of Power, Kali";
monsterIdPicture = 2078;
absorb = new RealmList<>();
reduction = new RealmList<RealmElement>();
gravityList = new RealmList<RealmInt>();
types = new RealmList<RealmInt>();
targetHp = 37408889;
targetDef = 0;
currentHp = targetHp;
beforeGravityHP = currentHp;
beforeDefenseBreak = targetDef;
targetElement = new RealmList<>();
targetElement.add(new RealmElement(4));
targetElement.add(new RealmElement(0));
gravityPercent = 1;
damageThreshold = 200000;
damageImmunity = 1000000;
isDamaged = false;
hasReduction = true;
hasDamageThreshold = false;
hasDamageImmunity = false;
reduction.add(new RealmElement(0));
reduction.add(new RealmElement(1));
reduction.add(new RealmElement(2));
reduction.add(new RealmElement(3));
reduction.add(new RealmElement(4));
types.add(new RealmInt(5));
types.add(new RealmInt(4));
types.add(new RealmInt(1337));
reductionValue = 50;
}
...
And finally the object creation under MainActivity. I simply want to make a new "enemy" object if there doesn't exist one with the id of 0 which is used as the default enemy for the user.
https://github.com/pookieofdoom/DamageCalculator/blob/master/app/src/main/java/com/padassist/MainActivity.java
if(realm.where(Enemy.class).equalTo("enemyId", 0).findFirst() == null){
enemy = new Enemy();
realm.beginTransaction();
realm.copyToRealmOrUpdate(enemy);
realm.commitTransaction();
} else {
enemy = realm.where(Enemy.class).equalTo("enemyId", 0).findFirst();
}
On the initial run of the app, it queries the realm fine without a transaction, creates the enemy object, stores it into the realm, and then passes it on. Upon restarting the app, any query that uses the Enemy class requires a transaction. You can see the other queries I have in the MainActivity for Team, Monster, or BaseMonster don't have transactions. This is the first time I have encountered something like this. When querying for an enemy, it seems it returns a brand new enemy through the constructor each time. I'm not exactly sure what is happening here and I believe the steps are easily reproducible. I don't believe Parceler is interfering at all either since for Monster and Team, the default constructors are not being called. If I leave the empty constructor alone and use a completely new one, the transaction crash does not happen but I'm not sure if this is the correct practice.
Code Sample
Incase you wanted the stack trace as well.
java.lang.RuntimeException: Unable to start activity ComponentInfo{com.padassist/com.padassist.MainActivity}: java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2484)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2544)
at android.app.ActivityThread.access$900(ActivityThread.java:150)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.app.ActivityThread.main(ActivityThread.java:5845)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
Caused by: java.lang.IllegalStateException: Changing Realm data can only be done from inside a transaction.
at io.realm.internal.Table.throwImmutable(Table.java:1285)
at io.realm.internal.Table.checkImmutable(Table.java:947)
at io.realm.internal.Table.addEmptyRow(Table.java:375)
at io.realm.Realm.createObjectInternal(Realm.java:823)
at io.realm.RealmElementRealmProxy.copy(RealmElementRealmProxy.java:242)
at io.realm.RealmElementRealmProxy.copyOrUpdate(RealmElementRealmProxy.java:232)
at io.realm.DefaultRealmModuleMediator.copyOrUpdate(DefaultRealmModuleMediator.java:290)
at io.realm.Realm.copyOrUpdate(Realm.java:1454)
at io.realm.Realm.copyToRealm(Realm.java:883)
at io.realm.RealmList.copyToRealmIfNeeded(RealmList.java:280)
at io.realm.RealmList.add(RealmList.java:197)
at com.padassist.Data.Enemy.<init>(Enemy.java:67)
at io.realm.EnemyRealmProxy.<init>(EnemyRealmProxy.java:0)
at io.realm.DefaultRealmModuleMediator.newInstance(DefaultRealmModuleMediator.java:239)
at io.realm.BaseRealm.get(BaseRealm.java:531)
at io.realm.RealmQuery.findFirst(RealmQuery.java:2070)
at com.padassist.MainActivity.onCreate(MainActivity.java:305)
at android.app.Activity.performCreate(Activity.java:6248)
at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1125)
at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2437)
at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2544)
at android.app.ActivityThread.access$900(ActivityThread.java:150)
at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1394)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:168)
at android.app.ActivityThread.main(ActivityThread.java:5845)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:797)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:687)
I thank you for your time to read this and appreciate any help that you have to offer. I'm sure it's something simple I completely overlooked but for now, I am stumped.
Version of Realm and tooling
Realm version(s): 2.2.0
Realm sync feature enabled: No.
Android Studio version: 2.2.2
Which Android version and device: HTC One M8 6.0, HTC One M7 5.0.1
Goal
Expected Results
Actual Results
Steps & Code to Reproduce
Code Sample
Version of Realm and tooling
Realm version(s): 2.2.0
Realm sync feature enabled: No.
Android Studio version: 2.2.2
Which Android version and device: HTC One M8 6.0, HTC One M7 5.0.1