Android: Realm + Retrofit 2 + Gson

Why writing all these custom serializers when you can make Gson and
Realm work together with just ONE LINE OF CODE?

TL;DR.

You can simply solve this by passing unmanaged RealmObjects to your Retrofit calls.

MyModel model = realm.where(MyModel.class).findFirst();
MyModel unmanagedModel = realm.copyFromRealm(model);
// then pass unmanagedModel to your retrofit calls

If you don’t want to go through all this answer, then skip to the “Recommended solutions” section posted down below.

Long talk (verbose answer)

This has nothing to do with Retrofit. If you have set Gson to be the data converter to your current Retrofit instance, then you can be sure that it’s Gson who’s failing.

Suppose we have this Model:

public class Model extends RealmObject {
    @PrimaryKey
    long id;
    boolean happy;

    public Model() {/* Required by both Realm and Gson*/}

    public Model(long id, boolean happy) {
        this.id = id;
        this.happy = happy;
    }

    public long getId() {
        return id;
    }

    public boolean isHappy() {
        return happy;
    }
}

For this code, we’ll have no issue:

Model unmanagedModel = new Model(5, true); // unmanagedModel
new Gson().toJson(unmanagedModel);   // {id : 5, happy : true}

But for this one:

Realm realm = /*...*/;
Model managedModel = realm.copyToRealm(unmanagedModel);
new Gson().toJson(managedModel); // {id : 0, happy : false}

// We'll get the samething for this code
Model anotherManagedModel = realm.where(Model.class).equalTo("id",5).findFirst();
new Gson().toJson(anotherManagedModel); // {id : 0, happy : false}

We’ll be surprised. We’re seeing nulls everywhere!.

Why?

Gson fails serializing a RealmObject only if it’s a managed one. Which means that there’s currently an opened Realm instance making sure this RealmObject is reflecting what is currently held in the persistence layer (the Realm database).

The reason why this is happening is due to the conflicting nature of how both Gson and Realm work. Quoting Zhuinden on why Gson sees null everywhere:

… that’s because GSON tries to read the fields of the
Realm object via reflection, but to obtain the values, you need to use
accessor methods – which are automatically applied to all field access
in the code via the Realm-transformer, but reflection still sees nulls
everywhere…

Christian Melchior proposes a workaround to this conflict by writing a custom JsonSerializers to every created Model. This is the workaround you have used, but I would NOT recommend it. As you have realized, it requires writing a lot of code which is error prone and the worst of all, kills what Gson is about (which is making our life less painful).

Recommended solutions

If we can somehow make sure the realmObject we pass to Gson is not a managed one, we’ll avoid this conflict.

Solution 1

Get a copy in memory of the managed RealmObject and pass it to Gson

new Gson().toJson(realm.copyFromRealm(managedModel));

Solution 2

(Wrapping the 1st solution). If the 1st solution is too verbose for you, make your models look like this one:

public class Model extends RealmObject {
    @PrimaryKey
    long id;
    boolean happy;
    
    // Some methods ...

    public Model toUnmanaged() {
        return isManaged() ? getRealm().copyFromRealm(this) : this;
    }
}

And then, you can do something like this:

// always convert toUnmanaged when serializing
new Gson().toJson(model.toUnmanaged());

Solution 3

This one is NOT very practical but is worth mentioning. You can go with deep-cloning your models (taken from here).

1 – Create a generic interface CloneableRealmObject:

interface CloneableRealmObject<T> {
    T cloneRealmObject();
}

2 – Make your realmObjetcs implement the above interface like so:

public class Model extends RealmObject implements CloneableRealmObject<Model> {
    @PrimaryKey
    long id;

    public Model() {
        // Empty constructor required by Realm.
    }

    @Override
    public Model cloneRealmObject() {
        Model clone = new Model();
        clone.id = this.id;
        return clone;
    }
}

3 – Clone the object before passing to your Retrofit calls.

new Gson().toJson(model.cloneRealmObject());

In a recent post

I gave an answer explaining why we’re getting this weird serialized output when using managed realmObjects. I recommend you to take a look at it.

Bonus

You might also want to check RealmFieldNamesHelper, a library made by Christian Melchior “to make Realm queries more type safe”.

Leave a Comment