Proper way to get Realm object by its primary key in Android Java
Solution 1
This is why I have a Realm repository like this one (which I wrote)
public class CalendarEventRepositoryImpl
extends LongRealmRepositoryImpl<CalendarEvent>
implements CalendarEventRepository {
public CalendarEventRepositoryImpl() {
super(CalendarEvent.class);
}
@Override
public Long getId(CalendarEvent calendarEvent) {
return calendarEvent.getId();
}
public void setId(CalendarEvent calendarEvent, Long id) {
calendarEvent.setId(id);
}
public String getIdFieldName() {
return CalendarEventFields.ID;
}
}
and I inherit a method called findOne(realm, id);
like
CalendarEvent event = calendarEventRepository.findOne(realm, id);
But yes, by default, it's realm.where(CalendarEvent.class).equalTo("id", id).findFirst();
Solution 2
I've ended up creating a helper class for this. I'll be using it until the Realm team implement these methods. I've named the class Find
(you can rename it the way you like, since naming things and cache invalidation are the hardest things in computer science). I think it's better to use this than to call where().equalTo()
passing the name of the primary key as a string value. This way you're sure to use the correct primary key field. Here is the code:
import java.util.Hashtable;
import io.realm.Realm;
import io.realm.RealmModel;
import io.realm.RealmObjectSchema;
public final class Find {
// shared cache for primary keys
private static Hashtable<Class<? extends RealmModel>, String> primaryKeyMap = new Hashtable<>();
private static String getPrimaryKeyName(Realm realm, Class<? extends RealmModel> clazz) {
String primaryKey = primaryKeyMap.get(clazz);
if (primaryKey != null)
return primaryKey;
RealmObjectSchema schema = realm.getSchema().get(clazz.getSimpleName());
if (!schema.hasPrimaryKey())
return null;
primaryKey = schema.getPrimaryKey();
primaryKeyMap.put(clazz, primaryKey);
return primaryKey;
}
private static <E extends RealmModel, TKey> E findByKey(Realm realm, Class<E> clazz, TKey key) {
String primaryKey = getPrimaryKeyName(realm, clazz);
if (primaryKey == null)
return null;
if (key instanceof String)
return realm.where(clazz).equalTo(primaryKey, (String)key).findFirst();
else
return realm.where(clazz).equalTo(primaryKey, (Long)key).findFirst();
}
public static <E extends RealmModel> E byKey(Realm realm, Class<E> clazz, String key) {
return findByKey(realm, clazz, key);
}
public static <E extends RealmModel> E byKey(Realm realm, Class<E> clazz, Long key) {
return findByKey(realm, clazz, key);
}
}
Usage is straightforward:
// now you can write this
EventInfo eventInfo = Find.byKey(realm, EventInfo.class, eventInfoId);
// instead of this
EventInfo eventInfo = realm.where(EventInfo.class).equalTo("id", eventInfo.id).findFirst();
It'll return null if there is no primary key for the given object or if the object is not found. I considered throwing an exception if there were no primary key, but decided it was overkill.
I was really sad Java generics are not as powerful as C# generics, because I really, really would love to call the method as follows:
Find.byKey<EventInfo>(realm, eventInfoId);
And believe me I tried! I've searched everywhere how to get a method's generic type return value. When it proved impossible, since Java erases the generic methods, I tried creating a generic class and use:
(Class<T>)(ParameterizedType)getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
And all the possible permutations to no avail! So I gave up...
Note: I've only implemented String and Long versions of Find.byKey
because Realm accepts only String and Integral data as primary keys, and Long will allow querying for Byte and Integer fields too (I hope!)
Solution 3
Am I missing some method?
Nope. As beeender mentioned, it's not implemented currently. Progress/discussion can be tracked here.
A helper function could look like this
public class Cat extends RealmObject {
@PrimaryKey
private int id;
private String name;
public Cat getByPrimaryKey(Realm realm, int id) {
return realm.where(getClass()).equalTo("id", id).findFirst();
}
}
It a class-specific method here because each class could have a different primary key type. You have to pass a realm instance to it that you manage in the calling class.
Solution 4
With the new Realm you can access to the primary key of your table using the schema of your DB in this manner:
private String load(Class realmClass) {
return mRealm.getSchema().get(realmClass.getSimpleName()).getPrimaryKey();
}
I'm not very expert in annotation, I've tried to read value of @PrimaryKey
at runtime by reflection, using getAnnotation()
on fields, but values are always null
, maybe because of @Retention(RetentionPolicy.CLASS)
.
Loudenvier
All I have been doing since 11 years old: var bio = new Dictionary<string, string>() { { "Judo", "Black-belt" }, { "Brazilian Jiu-Jitsu", "Brown-belt" }, { "Bodyboarding", "For 24 years" }, { "Literature", "Voltaire, Dickens, Gibson, Adams, Zelansny..." }, { "Music", "Symphony X, Dream Theater, Bad Religion, Glenn Hughes, ..." }, { "My wife!", "Forever" } }; Sorry, forgot one thing: programming computers (now mostly in C#).
Updated on July 05, 2022Comments
-
Loudenvier almost 2 years
I wonder if there's a proper way to retrieve an object given its primary key in Realm for Android. I know the method
objectForPrimaryKey
does exists in Swift but there seems to be no such counterpart in Realm for Android. I really think that doingrealm.where(EventInfo.class).equalTo("id", eventInfo.id).findFirst();
looks like a lot of waste (at least it is not wrist-friendly). Am I missing some method? I'm Currently using Realm 1.0.1 -
Tim almost 8 yearsI had this in mind, but then changed my answer because it's not a given that getId returns a long for all models, how do you deal with it?
-
EpicPandaForce almost 8 yearsI extend from
StringRealmRepositoryImpl
orNoIdRealmRepositoryImpl
depending on the case -
Loudenvier almost 8 yearsI don't like the repository pattern, I do prefer to use the ORM directly. In my experience repositories are more of a pain than any help. The ORM IS a kind of repository, as it allows access and query to the underlying data directly tied to the object model. Martin Fowler defined repositories as an "object that mediates between the domain and data mapping layers”. That's exactly what the ORM is for IMHO :-). So I'll probably subclass Realm itself and add the missing methods :-)
-
EpicPandaForce almost 8 years
public final class Realm extends BaseRealm {
-
Loudenvier almost 8 years@EpicPandaForce Yes, it's final. Realized it earlier today. I've ended up creating a helper class named Find. I'm posting the solution right now
-
EpicPandaForce over 7 years
(Class<T>)(ParameterizedType)getClass() .getGenericSuperclass()).getActualTypeArguments()[0];
this works only with type-parametric anonymous subclasses such asnew TypeToken<EventInfo>() {};
. It can actually also returnList<EventInfo>
for example, it's how GSON works for example. But a bit more difficult to type thanEventInfo.class
-
M.kazem Akhgary over 5 yearsis this
O(1)
operation? it seemsO(n)
by the looks of it, but maybe@PrimaryKey
is handled differently? -
EpicPandaForce over 5 yearsThey have some form of index over primary keys so I'd assume something like O(log n)