Realm-java: ClassCastException when trying to convert a field from int to integer list

Created on 11 Aug 2016  ·  9Comments  ·  Source: realm/realm-java

Hello,
I want to convert an existing int in my RealmObject to a RealmList containing a custom integer wrapper class. In my migration class I have some code to create the new list field, migrate the data from the old field to the new field and delete the old field. The code is somewhat adapted from here.

Goal

Convert a int field to a realmlist containing integers in a custom wrapper class, as realm doesn't currently support RealmList<Integer>.

Expected Results

I can convert the field and transform all the data from the old field to the new field using transform.

Actual Results

The app crashes as soon as I open Realm and the migration gets executed.

Caused by: java.lang.ClassCastException: com.example.RealmInteger cannot be cast to io.realm.internal.RealmObjectProxy at io.realm.DynamicRealmObject.setList(DynamicRealmObject.java:602) at com.example.RealmHelper$Migration$1.apply(RealmHelper.java:132) at io.realm.RealmObjectSchema.transform(RealmObjectSchema.java:475) at com.example.RealmHelper$Migration.migrate(RealmHelper.java:126) at io.realm.BaseRealm$3.onResult(BaseRealm.java:705) at io.realm.RealmCache.invokeWithGlobalRefCount(RealmCache.java:267) at io.realm.BaseRealm.migrateRealm(BaseRealm.java:685) at io.realm.Realm.migrateRealm(Realm.java:1221) at io.realm.Realm.migrateRealm(Realm.java:1208) at io.realm.Realm.createInstance(Realm.java:235) at io.realm.RealmCache.createRealmOrGetFromCache(RealmCache.java:126) at io.realm.Realm.getDefaultInstance(Realm.java:174) at com.example.RealmHelper.init(RealmHelper.java:86) at com.example.AppApplication.onCreate(AppApplication.java:95) at android.app.Instrumentation.callApplicationOnCreate(Instrumentation.java:1014) at android.app.ActivityThread.handleBindApplication(ActivityThread.java:4751) at de.robv.android.xposed.XposedBridge.invokeOriginalMethodNative(Native Method)  at de.robv.android.xposed.XposedBridge.handleHookedMethod(XposedBridge.java:729)  at android.app.ActivityThread.handleBindApplication(<Xposed>)  at android.app.ActivityThread.-wrap1(ActivityThread.java)  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1424)  at android.os.Handler.dispatchMessage(Handler.java:102)  at android.os.Looper.loop(Looper.java:148)  at android.app.ActivityThread.main(ActivityThread.java:5461)  at java.lang.reflect.Method.invoke(Native Method)  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:726)  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:616)  at de.robv.android.xposed.XposedBridge.main(XposedBridge.java:133) 

Steps & Code to Reproduce

Describe your current debugging efforts.

Code Sample

RealmInteger:

public class RealmInteger extends RealmObject {

    private int value;

    public RealmInteger() {
    }

    public RealmInteger(int value) {
        this.value = value;
    }

    public int getValue() {
        return value;
    }

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

Migration:

RealmObjectSchema integerSchema = schema.create("RealmInteger")
        .addField("value", int.class);

RealmObjectSchema objectSchema = schema.get("CustomObject");

// Remove trip and add trips
objectSchema.addRealmListField("trips", integerSchema)
        .transform(new RealmObjectSchema.Function() {
            @Override
            public void apply(DynamicRealmObject object) {
                RealmList<RealmInteger> list = new RealmList<>();
                list.add(new RealmInteger(object.getInt("trip")));

                object.setList("trips", list);
            }
         })
         .removeField("trip");

Basically, I want to convert

public class CustomObject extends RealmObject {
    private int trip;
}

to

public class CustomObject extends RealmObject {
    private RealmList<RealmInteger> trips;
}

and possibly preserve the existing data (e.g. if trip contains 1, trips should contain [1]) .

Version of Realm and tooling

Realm version(s): 1.0.0

Android Studio version: 2.2 Beta

Which Android version and device: 6.0.1 CM13 Galaxy S5

Most helpful comment

Oh sorry for the confusion, didn't see the update you made to your previous post. Your code snipped works fine now. Thank you! :)

All 9 comments

This is clearly a result of the fact that RealmIntegers created in your apply method aren't managed and you can only set an object list link if all objects inside it are managed, so you have to create the RealmIntegers directly through the DynamicRealm, so if I'm right, you should replace

        @Override
        public void apply(DynamicRealmObject object) {
            RealmList<RealmInteger> list = new RealmList<>();
            list.add(new RealmInteger(object.getInt("trip")));

            object.setList("trips", list);
        }

with

       @Override
       public void apply(DynamicRealmObject object) {
           RealmList<RealmInteger> list = new RealmList<>();
           RealmInteger realmInt = realm.createObject(RealmInteger.class.getName());
           realmInt.value = object.getInt("trip");
           list.add(realmInt);
           object.setList("trips", list);
       }

Thanks for your suggestion, but unfortunately your code snipped doesn't work.
DynamicRealm.createObject returns a DynamicRealmObject, but a RealmInteger is required (at least that's what AS is telling me).

Also I am a bit confused as to why setting a list containing unmanaged objects inside a migration doesn't throw the same error as it would if I would try to add them outside a migration (java.lang.IllegalArgumentException: Each element of 'value' must be a valid managed object.).

Oh you are correct, please change

 RealmList<RealmInteger> list = new RealmList<>();

to

RealmList<DynamicRealmObject> list = new RealmList<>();

and

RealmInteger realmInt = realm.createObject(RealmInteger.class.getName());

to

DynamicRealmObject realmInt = realm.createObject(RealmInteger.class.getName());
realmInt.setInt("value", object.getInt("trip"));

So in the end it should be

       @Override
       public void apply(DynamicRealmObject object) {
           RealmList<DynamicRealmObject> list = new RealmList<>();
           DynamicRealmObject realmInt = realm.createObject(RealmInteger.class.getName());
           realmInt.setInt("value", object.getInt("trip"));
           list.add(realmInt);
           object.setList("trips", list);
       }

Sorry, but the error is in RealmInteger realmInt = realm.createObject(RealmInteger.class.getName());.

Initialising the list by using RealmList<RealmInteger> list = new RealmList<>(); works fine.

No I'm pretty sure it should be

@Override
public void apply(DynamicRealmObject object) {
    RealmList<DynamicRealmObject> list = new RealmList<>();
    DynamicRealmObject realmInt = realm.createObject(RealmInteger.class.getName());
    realmInt.setInt("value", object.getInt("trip"));
    list.add(realmInt);
    object.setList("trips", list);
}

Try it

Oh sorry for the confusion, didn't see the update you made to your previous post. Your code snipped works fine now. Thank you! :)

Glad I could help! :)

@beeender I think this one needs a better error message, though.

Please be careful that RealmInteger.class.getName() does now work as expected with ProGuard without additional configuration since the name of RealmInteger class is modified.

This will be fixed with https://github.com/realm/realm-java/pull/3290

Nice find :smile:

(as for proguard, I've just been using this

#realm
-keepnames public class * extends io.realm.RealmObject
-keep @io.realm.annotations.RealmModule class *
-keep class io.realm.** { *; }
-dontwarn javax.**
-dontwarn io.realm.**

#realm 0.84.1+
-keep class io.realm.annotations.RealmModule
-keep @io.realm.annotations.RealmModule class *
-keep class io.realm.internal.Keep
-keep @io.realm.internal.Keep class *
-dontwarn javax.**
-dontwarn io.realm.** 

)

Was this page helpful?
0 / 5 - 0 ratings