Search code examples
androidrealm

Cannot retrieve field values from realm object, values are null in debugger


It seems like my RealmObject values are being hidden by the RealmProxy class, but can be set from the proxyclass.

My model is pretty straight forward as you can see.

public class GroupRealm extends RealmObject {
    @PrimaryKey
    public String id;
    @Index
    public String name;
    public String imageUrl;
    public int order;

    public GroupRealm parent;
    public RealmList<GroupRealm> children;
    public RealmList<ContentRealm> contents;
}

This is how i am setting the values(db is a valid Realm, and everything is in a transaction that commits fine):

GroupRealm gr = db.where(GroupRealm.class).equalTo("id",g.GroupID).findFirst();
        if(gr==null){
            gr = db.createObject(GroupRealm.class,g.GroupID);
        }
        gr.imageUrl = g.GlyphUrl;
        gr.name = g.Title;
        gr.order = g.OrderNum;

The image below is what I get when i query the db latter on.(same variable name not same place in code)

RealmObject has values RealmProxyClass does not

In my android.library where my RealmObjects are defined project I have the necessary plugins.

apply plugin: 'com.android.library'
apply plugin: 'realm-android'

and on the project level I am setting the correct dependencies:

dependencies {
    classpath 'com.android.tools.build:gradle:2.1.0'
    classpath "io.realm:realm-gradle-plugin:0.90.1"
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
}

I am out of ideas. If I try to access anything I retrieve the GroupRealm as expected but all of the public properties exposed through the proxy class return null!


Solution

  • Relevant FAQ in documentation: https://realm.io/docs/java/latest/#debugging


    Realm uses Android Gradle Transform API. It gives a possibility to manipulate compiled class files before they are converted to dex files.
    More details inside io.realm.transformer.RealmTransformer and io.realm.transformer. BytecodeModifier classes which can be found in the realm's github.

    What RealmTransformer does, among others, is:

    • replacing all accesses to fields of user's RealmObjects with the appropriate Realm accessors.

    You can also check result classes inside folder app/build/intermediates/transforms/RealmTransformer/

    Example of setter:
    Line of your code:

    gr.imageUrl = g.GlyphUrl;
    

    will be replaced with something like this:

    String var5 = g.GlyphUrl;
    gr.realmSet$imageUrl(var5);
    

    Example of getter:

    String url = gr.imageUrl;
    

    will be replaced with something like this:

    String url = gr.realmGet$imageUrl();
    

    Example use case

    1. You have created class GroupRealm. Realm using Transform API generates GroupRealmRealmProxy. This proxy class looks like this:

      public class GroupRealmRealmProxy extends GroupRealm implements RealmObjectProxy, GroupRealmRealmProxyInterface {
          private final GroupRealmRealmProxy.GroupRealmColumnInfo columnInfo;
          private final ProxyState proxyState;
          private RealmList<GroupRealm> childrenRealmList;
          private RealmList<ContentRealm> contentsRealmList;
          private static final List<String> FIELD_NAMES;
      
          GroupRealmRealmProxy(ColumnInfo columnInfo) {
              ...
          }
      
          public String realmGet$id() {
              this.proxyState.getRealm$realm().checkIfValid();
              return this.proxyState.getRow$realm().getString(this.columnInfo.idIndex);
          }
      
          public void realmSet$id(String value) {
              this.proxyState.getRealm$realm().checkIfValid();
              if(value == null) {
                  this.proxyState.getRow$realm().setNull(this.columnInfo.idIndex);
              } else {
                 this.proxyState.getRow$realm().setString(this.columnInfo.idIndex, value);
              }
          }
      
          public String realmGet$name() {
              this.proxyState.getRealm$realm().checkIfValid();
              return this.proxyState.getRow$realm().getString(this.columnInfo.nameIndex);
          }
      
          public void realmSet$name(String value) {
              this.proxyState.getRealm$realm().checkIfValid();
              if(value == null) {
                  this.proxyState.getRow$realm().setNull(this.columnInfo.nameIndex);
              } else {
                  this.proxyState.getRow$realm().setString(this.columnInfo.nameIndex, value);
              }
           }
      
          ...
      }
      

      You can observe that methods realmSet$name and realmGet$name don't have access to field name declared in the class GroupRealm. They use proxyState.

    2. Now, let's back to the usage of GroupRealm. When you debug your code:

      GroupRealm gr = db.where(GroupRealm.class).equalTo("id",g.GroupID).findFirst();
      if(gr==null){
          gr = db.createObject(GroupRealm.class,g.GroupID);
      }
      gr.imageUrl = g.GlyphUrl;
      gr.name = g.Title;
      gr.order = g.OrderNum;
      

      in a reality it's decompiled version looks like this:

      GroupRealm gr = (GroupRealm)realm.where(GroupRealm.class).equalTo("id", g.GroupId).findFirst();
      if(gr == null) {
          gr = (GroupRealm)realm.createObject(GroupRealm.class, g.GroupId);
      }
      
      String var7 = g.GlyphUrl;
      gr.realmSet$imageUrl(var7);
      var7 = g.Title;
      gr.realmSet$name(var7);
      int var8 = g.OrderNum;
      gr.realmSet$order(var8);
      

      First of all, gr is the instance of GroupRealmRealmProxy class. As you can see, setting of gr.name is replaced by gr.realmSet$name(var7). It means that the field name of GroupRealm is never used. The situation is analogous in the case of realmGet$.

    While debugging you see your version of source code but actually you're using a modified version with injected methods realmSet$ and realmGet$.