Android: Realm + Retrofit 2 + Gson

前端 未结 2 1214
青春惊慌失措
青春惊慌失措 2020-11-27 23:04

I have a problem when using Retrofit + Gson and Realm. I know that there is an issue with the combination of these 3 libraries. Some answers sugge

相关标签:
2条回答
  • 2020-11-27 23:19

    I too faced the similar issue. This is because your request format is wrong. In my case, I am trying to send a Realm object by getting it from local SQLite DB instead of Java object. Retrofit converts only Java object to JSON but not Realm object. Please make sure you are sending a right JSON as a request when using Retrofit.

    Then I replaced this:

    List<MyRealmModel> objectsToSync = mRealm.where(MyRealmModel.class).findAll();
    

    To:

    List<MyRealmModel> objectsToSend = mRealm.copyFromRealm(objectsToSync);
    
    0 讨论(0)
  • 2020-11-27 23:26

    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.

    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".

    0 讨论(0)
提交回复
热议问题