Search code examples
javaandroidgsonrealm

Composition over inheritance for RealmObjects with Gson serialization


I'm considering Realm as a database solution for various reasons, but the big one currently being the TransactionTooLargeException now being thrown in Nougat has made it so I have to rework my current database architecture, based on ActiveAndroid, which has its own annoying limitations. The difficulty is that Realm does not support inheritance (https://github.com/realm/realm-java/issues/761) and they don't seem in any particular hurry to get around to it. Instead, they recommend using composition over inheritance, but I can't figure out how to make that work with Gson/Json deserialization.

Example:

Superclass: Animal, with subclasses Dog and German Shepherd

public class Animal {

    private int numLegs;

    private boolean hasFur;
}

public class Dog extends Animal {
    private String color;

    private boolean canDoTricks;
}

public class GermanShepherd extends Dog {

    public boolean isGuardDog;

    public boolean isAtRiskOfHipDysplasia()
}

(Sorry, this is a super canned example, just to illustrate).

Now let's say the json for this looks like:

{
  "numLegs" : 4,
  "hasFur" : true,
  "color" : "Black & Brown",
  "canDoTricks" : true,
  "isGuardDog" : true,
  "isAtRiskofHipDysplasia" : false
}

Now, I cannot modify the Json because it's an API that's giving it to me.

Looking at this answer: https://stackoverflow.com/a/41552457/4560689, it appears it is possible in a very hacky way to make it sort of work, but the answer notes that there are limitations including that the serialization would be wrong. Since the server only talks in the json format that doesn't involve crazy composition, this presents a problem.

Can I write a custom Gson deserializer/serializer to make this work? If so, what would that look like? I basically need to be able to convert a json payload into up to N objects, where N - 1 objects are nested inside the base object.

So with composition (note this isn't necessarily "Realm" composition, just an example, since it looks like Realm has to use some weird form of interface-composition), I'd have a class like below:

public class GermanShepherd {
    public Animal animal;
    public Dog dog;

   // Generate a bunch of delegate methods here
}

Am I barking up the wrong tree? It feels like Realm might not work for what I'm trying to do, and inheritance is built into the API I'm using in multiple places, and specifically in the objects I want to persist, so I have to either figure out a workaround or use another solution. ActiveAndroid (what I'm using now) is also a less than ideal solution and I'm sick to death of dealing with skirting around deadlocks, crashes, querying on background threads that now cause crashes if the data is too big to pass in an Intent, etc...all issues with SQLite. I'm open to solutions to my main question or to alternatives that would solve this problem. Thanks in advance for your help!


Solution

  • You should create a new RealmObject class for each flattened concrete class, and map your JSON representation to them.

    To retain inheritance, you can simulate it by inheriting getters/setters from interfaces that inherit from one another.

    public interface IAnimal extends RealmModel {    
        int getNumberOfLegs();
        void setNumberOfLegs(int legs);
        boolean getHasFur();
        void setHasFur(boolean hasFur);
    }
    
    public interface IDog extends IAnimal {
        String getColor();
        void setColor(String color);
        boolean getCanDoTricks();
        void setCanDoTricks();
    }
    
    public interface IGermanShepherd extends IDog {
        boolean getIsGuardDog();
        void setIsGuardDog(boolean isGuardDog);
        boolean getIsAtRiskOfHipDysplasia();
        void setIsAtRiskOfHipDysplasia(boolean isAtRisk);
    }
    

    Because then you can do

    public class GermanShepard 
        extends RealmObject 
        implements IGermanShepard {
          private int numLegs;
          private boolean hasFur;
          private String color;
          private boolean canDoTricks;
          private boolean isGuardDog;
          private boolean isAtRiskofHipDysplasia;
    
          // inherited getters/setters
    }
    

    You can even make repository class out of it

    public abstract class AnimalRepository<T extends IAnimal> {
         protected Class<T> clazz;
    
         public AnimalRepository(Class<T> clazz) {
             this.clazz = clazz;
         }
    
         public RealmResults<T> findAll(Realm realm) {
             return realm.where(clazz).findAll();
         }
    }
    
     @Singleton
     public class GermanShepardRepository extends AnimalRepository<GermanShepard> {
          @Inject
          public GermanShepardRepository() {
              super(GermanShepard.class);
          }
     }
    

    And then

    @Inject
    GermanShepardRepository germanShepardRepository;
    
    RealmResults<GermanShepard> results = germanShepardRepository.findAll(realm);
    

    But you can indeed merge them into one class and then give it a String type; parameter to know what type it originally was. That's probably even better than having all these GermanShepards.