I’ve already written a few tips on how to generally use Realm, but I saw a particular RealmModel written by someone on Stack Overflow, and I felt like I should probably share a few tips on how to design a proper Realm schema, or at least simplify things for the future.
It was the following:
There’s lots of pain involved with this set of Realm objects.
Before we get to that, we should understand a few things:
A concrete example.
If you have a Product class, which has a quantity and price field, and you want to sort based on (quantity*price), then you obviously can’t express this with a RealmQuery, because custom predicates like that aren’t supported.
What you CAN however do is defined a THIRD property called “total”, and update its value in your setters.
Because then you can do
And of course, if you use a field in a query, you should definitely put an index on it.
There’s this great side thing (unofficially) for Realm by C. Melchior called RealmNamesFieldHelper which runs an annotation processor and creates these ___Fields classes for you.
Now you won’t need to make your own constants, and you definitely won’t want to directly write field names by hand. Who does that anyways?
The class I showed at the beginning is a perfect example on how to complicate your life when using Realm.
Separating that one Email class into so many classes makes you need to know their parents (or even parents’ IDs) to query by them efficiently, and this relation would have to be set-up manually because backlinks aren’t yet supported.
If you query Emails by their Sender, you’ll end up using link queries, which are slower and somewhat more complicated to use (and you can’t sort via a link yet).
Adding unnecessary object links also make you need to manage their deletion. To delete all objects that belong to a single Email, you would need to traverse them and call deleteFromRealm() manually on each of them.
So to refresh our minds, here it is again:
And here’s the simplified version:
Boom, so much easier to manage! Now you don’t need to tinker with link queries, worry about link sorts, and manage cascade deletes by hand!
Having separate objects of unidirectional links gave us no benefit.
This of course cannot be done if you have many-relations (RealmList) between the objects. Unfortunately for us, then we’ll still need to separate them.
This only applies before queries across
@LinkingObjects are introduced.
If you can’t avoid many relations between your objects, and you can’t use linking objects, then you should still consider being able to create RealmResults for both sides of the relation.
For this, you ought to set up the IDs on both sides, kinda like joins (except as you know, there are no joins here).
A not-so-concrete example:
This way you can query for Tags that belong to a particular post, and you don’t have to manually search through the list in the Post object.
Since queries across
@LinkingObjects were introduced, you don’t need to set up bidirectional links. One link to the other will always create an inverse link, which works just like a normal link would.
No, but you can store a field that contains the “key” (it could be a String or a primary key of another object). Querying for that “key” field will give you your entry set as a RealmResults for that particular “key”.
You can generally avoid maps in your Realm objects, and convert them to queryable fields.
A concrete example.
The rank of posts were returned to me by the server, and these ranks were specified by the server. It was paginated too, so you couldn’t just evaluate these ranks by hand on the client.
You had to show both the posts on a “normal” feed, and you had to be able to show pretty much the same objects via a “favorited” feed.
Needless to say, the primary keys matched. Ranks would have been overwritten. And the way “categories” were managed didn’t make this easier either.
I figured I’d just separate my objects into two different “tables”. So there was now a FeedItem, and a FavoriteFeedItem. I also had the feed logic that worked with the properties of a FeedItem, but I sure wouldn’t want to duplicate it and create a “FavoriteFeedView” and “FavoriteFeedAdapter”. No way.
Solution, share the fields via an interface.
Creating this interface allowed me to use RealmResults<FeedItemInterface> after some casting magic in my repository.
Woo! Now I could use FeedItemInterface everywhere, with no duplication in actual business logic code!
You can use a String qualifier to determine the type of object your RealmObject is from a set of possible objects, and use the fields that make sense in context of that given subtype. (This technique is also called “single-table inheritance”).
In my real-life case, this was the selection of its RealmList parameter. I’ll show the whole object just for sake of completeness.
ResultType had the possible value of FIRST, SECOND, THIRD or FOURTH. And depending on this value, that particular RealmList made sense to use. The others were all empty. But as such, this ResultPost had mostly unified logic around it.
I do admit there was some not very clean magic around it and storing logic in the enum that determined the result type, but it still simplified things for me.
But this part is getting very domain-specific, so I’ll leave it at that.
Hopefully that helped a bit regarding the definition of Realm schema. The greatest take-away is that you should try to minimize your uni-directional object links, unless it’s truly necessary.
Uni-directional objects links are common in the typically used workaround for primitive lists, for which you can read the follow-up article (about how
RealmList<RealmString> is bad, and you shouldn’t use it).
(Reddit discussion thread: https://www.reddit.com/r/androiddev/comments/576fl5/designing_the_schema_of_realm_effectively_and/ )