Realm Java 0.89 — Model and Collection interfaces!
We just released a new version of Realm Java to this website and to Bintray. This release is packed with fixes and new features.
RealmModel interface
A longstanding requirement for Realm has been that all Realm model classes must extend the base class RealmObject
.
Starting with 0.89, this is no longer a strict requirement and you can now choose to implement the new RealmModel
interface instead.
@RealmClass
public class implements RealmModel {
}
You will need to manually add the @RealmClass
annotation since inheriting annotations from interfaces is not yet supported by Android.
The helper methods on RealmObject are available as static methods, so if you use RealmModel
you can use these methods.
Person person = getPerson();
// Extending RealmObject
person.isValid();
person.addChangeListener(listener);
// Implementing RealmModel
RealmObject.isValid(person);
RealmObject.addChangeListener(person, listener);
Extending RealmObject
is still the recommended approach, but you now have the option of using the method which suits your architecture and codebase guidelines better.
Note that Realm Java still does not support extending anything other than RealmObject
. That feature is still being tracked here.
RealmCollection API
The Realm Java API currently consists of two collections: RealmResults
and RealmList
, which both implement the standard List
interface.
Both of these classes have been extended with additional Realm capabilities, but unfortunately their behavior had diverged slightly, which caused problems with RealmBaseAdapter
.
In order to provide a more consistent experience, we are now introducing two new interfaces: RealmCollection
and OrderedRealmCollection
.
This provides a solid foundation for adding future collections as well as unifying the behavior and naming of methods across the API.
It has a few implications:
The behavior of methods like remove
and clear
now only operate on the collection and not the underlying Realm data. This especially impacts RealmResults
where these methods will now throw UnsupportedOperationException
.
-
RealmBaseAdapter
now works out of the box with bothRealmList
andRealmResults
. -
Methods for deleting objects from both the collection and Realm are now called
deleteFromRealm()
ordeleteAllFromRealm()
. -
Realm.clear(Class)
andRealm.clear()
have been renamed toRealm.delete(Class)
andRealm.deleteAll()
. -
RealmObject.removeFromRealm()
has been renamed toRealmObject.deleteFromRealm()
.
Stable iterators
One of the design paradigms of Realm is the concept of auto-updating results. Normally that is a great feature to have, except in one case: Changing objects in a way that would remove them from the RealmResult
.
Take the following example:
RealmResults<Person> results = realm.where(Person.class).equalTo("inviteToGoogleIO", false).findAll();
// Try to invite all the people
realm.beginTransaction();
for (Person p : results) {
p.inviteToGoogleIO(true);
}
realm.commitTransaction();
With a normal collection, you would expect to have all persons invited to Google I/O, but due to RealmResults
being live-updated, it would actually only send invites to every other person. The reason is that due to the RealmResults
live-updating, it would adjust the size of the collection the moment you set the first boolean field to true
. This would in turn cause the iterator’s internal index to point to the next item (which hasn’t been updated), so when you call Iterator.next()
or similar, it would skip over an item.
The solution so far has been to iterate backwards as that takes into account the changing size()
:
for (int i = results.size() - 1; i >=0; i--) {
results.get(i).inviteToGoogleIO(true);
}
This is however very counter-intuitive. With 0.89, the auto-updating feature of RealmResults
have changed slightly so instead of RealmResults being live all the time, they are now only updated on Looper events.
This means that iterators will now work as expected but also introduces a slight chance that you might accidentally access a deleted item.
RealmResults<Person> results = realm.where(Person.class).findAll();
for (Person p : results) {
p.deleteFromRealm(); // Indirectly delete all items one-by-one.
}
// RealmResults are not updated until next Looper event
// So the RealmResult might now contain deleted objects
Person p = results.get(0);
p.isValid() == false;
// Deleting the item directly on the RealmResults will remove them
// from the RealmResults as well
results.deleteFromRealm(0);
// and new Queries will also exclude deleted objects
results = realm.where(Person.class).findAll(); // Deleted users are removed
Read more here.
This change also impacts all RealmChangeListener
s that were previously triggered after each call to Realm.commmitTransaction()
on the same thread. They are now deferred to the next Looper event just like changes from other threads. This should have no effect on normal app behavior, but could potentially cause issues in unit tests that assumed that callbacks where immediately notified.
Breaking changes 🚨
PrimaryKey fields are no longer automatically marked as @Required
. They can now be null
if the type of the field can usually be null.
This change will throw a RealmMigrationNeededException
. Either manually add @Required
to the primary field to maintain the same behavior as 0.88.3 and below, or change the nullability in a migration step.
RealmObjectSchema personSchema = schema.get("Person");
personSchema.setNullable("myPrimaryKey", true);
Other improvements
-
RealmConfiguration.initialData()
has been added. It makes it possible to populate a Realm file the first time it is created (thanks @thesurix). -
RealmObjectSchema.isPrimaryKey(fieldName)
has been added. -
It is no longer required to define an
ExclusionStrategy
when combining Realm and GSON.
See the full changelog for all the details.
Thanks for reading. Now go forth and build amazing apps with Realm! As always, we’re around on Stack Overflow, GitHub, and Twitter.