2

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!

Community
  • 1
  • 1
Chantell Osejo
  • 1,456
  • 15
  • 25
  • 1
    I'm the one who wrote the answer you posted. Well, there is a way to go around this by creating a GermanShepard POJO (non realmObject) that would be serialized the way you like with Gson and do some sort of Mapping between your GermanShepard's realmObject and this POJO. In fact, that's what ppl have been doing to workaround this issue. Check [this](http://johnpetitto.com/no-more-realm). If you want me to write an example about how to do that, just tell me and I'll post some code ^^. But I would recommend the custom serializers, that's a better approach. – Anis LOUNIS aka AnixPasBesoin Feb 15 '17 at 22:08
  • @AnixPasBesoin, thanks for the reply! I'd like to do the custom serializers, if possible. Do you have an idea of how I might make that work? Do I just use Gson as previously to deserialize the API's response into the RealmObject and then serialize the RealmObject back to the json format the API is expecting? – Chantell Osejo Feb 15 '17 at 22:40
  • Are Gson and other auto-serializers weird to anyone else? Personally, I think I prefer to just serialize/deserialize manually... some repetition (have to add new fields to serializer and deserializer), but I think it's a good layer of abstraction that adds control and flexibility – updating the api doesn't require updating the object's internals; just how it talks to the api. – Nolan Amy Feb 15 '17 at 22:56
  • @Nolan Gson's a pretty standard library to remove all that boilerplate code. Yes, I *could* manually serialize/deserialize everything, but that's prone to human error (aka bugs) and requires me to keep track of everything and ensure I'm appropriately serializing/deserializing for every field that gets added or removed. – Chantell Osejo Feb 15 '17 at 23:20
  • You should map the JSON responses into a Realm schema in which these hierarchies are flattened out. Most API responses don't result in an optimal Realm schema, so attempting to shoehorn GSON's responses into Realm's "box" is extremely shortsighted because your queries will be terrible or you just won't be able to do it (as you see here). - - - I've designed an API with Realm in mind, using was easy but a mapping was still required. – EpicPandaForce Feb 16 '17 at 00:22
  • @ChantellOsejo yep, not contesting whether they're popular :) – Nolan Amy Feb 16 '17 at 00:31
  • @EpicPandaForce Glad to hear from you that mapping is a good approach. Chantell you should trust EpicPandaForce, and check his articles about using Realm, they are very insightful. – Anis LOUNIS aka AnixPasBesoin Feb 16 '17 at 00:47

1 Answers1

3

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.

EpicPandaForce
  • 79,669
  • 27
  • 256
  • 428
  • If I understand correctly, GermanShepherd both extends RealmObject and implements RealmModel. Why is that? – Tim Feb 16 '17 at 10:12
  • declaration of `RealmObject` is actually `@RealmClass public abstract class RealmObject implements RealmModel`, so this shouldn't be a problem. It's to make `RealmResults` work in the animal repository. Also I haven't actually tested this, but I think it should work. – EpicPandaForce Feb 16 '17 at 11:45
  • But it sounds like use-case ["if you truly need polymorphism, you can share the fields of the RealmObject via an interface"](https://hackernoon.com/designing-the-schema-of-realm-effectively-and-other-realm-tips-feb76c5b6072#.32hs6ed05) – EpicPandaForce Feb 16 '17 at 11:57
  • @EpicPandaForce, this is going to likely be a dumb question, but in this instance, would it be really bad to just collapse all the fields into one class? In my actual use case, the subclasses are all basically just more detailed views of one super class. So there's a tiny object (makes queries faster) that can be returned with only say, 3-4 fields, then a subclass which adds another 4-5 fields, then another subclass that adds another 10 fields. It's not "truly" polymorphic in the sense that there's multiple distinct subclasses inheriting from the same superclass. – Chantell Osejo Feb 16 '17 at 19:44
  • 1
    nope, 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. – EpicPandaForce Feb 16 '17 at 20:04
  • 1
    Awesome. Thanks for clarifying. Also, your articles/tutorials on Realm are amazing. I wish I'd found them sooner, because they're really helping clarify how to use it properly. It's a big change moving from SQL to NoSQL and hard to wrap my head around with all the conflicting opinions. – Chantell Osejo Feb 16 '17 at 22:05