How to use Realm for Android like a champ, and how to tell if you’re doing it wrong
I’ve been using Realm for a long time (since v0.81.1), and I should note that I’m not affiliated with Realm. But since then, I’ve been keeping track of a lot of breaking changes, and have seen quite a few posts on how to use Realm… and most of them were wrong in some way or another. Overcomplicated, prone to errors, or just plain wrong.
The newest version at this time is v6.1.0, you can see the latest version on their website. You should probably update if you’re behind.
Anyways, here, I’ll tell you how to tell if an example you’re reading is bad, or if you’re bound to mess something up. But first, a very brief intro to Realm.
What is Realm?
Realm is a database system. It’s kinda like SQLite, except it has nothing to do with SQLite at all. You define classes, these classes define your schema, and Realm stores instances of this class as objects. It’s not an SQL database, it’s a NoSQL database. It’s fairly easy to use, and it has some pretty cool features too — but I’ll go into detail on that further down the article.
Anyways, back on track:
Typical errors people seem to make all the time
- using realm.beginTransaction() and realm.commitTransaction() instead of realm.executeTransaction(Realm.Transaction)
The problem with this is that executeTransaction()
automatically handles calling realm.cancelTransaction()
in case an exception is thrown, while the other alternative typically neglects the try-catch.
Yes, you’re supposed to call cancel on transactions that aren’t going to end up being committed.
For example, on background threads:
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- opening Realm instances with
Realm.getDefaultInstance()
that they never close anywhere
The problem with this is that on background threads, having an open Realm instance that you don’t close even when the thread execution is over is very costly, and can cause strange errors. As such, it’s recommended to close the Realm on your background thread when the execution is done in a finally block. This includes IntentServices.
You should also consider closing the Realm instance on your UI thread too when you no longer have Activities left, so you can compact your non-encrypted Realm on exit. You can’t do that if you have open instances lingering around. (Personally I have one global Realm instance for the UI thread, and close it when there are no open activities).
Here’s a rule: if you see code like Realm.getDefaultInstance().where(…)
it’s going to break sooner or later.
(Also, a tip: you should open your first Realm instance on the UI thread only after the first Activity has been created, just to be safe from context.getFilesDir() == null).
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- executing many transactions (for example, each element is added in a new transaction, in a for-loop) on non-looper not-autoupdating background threads
It’s going to cause you trouble at some point. Minimize your transaction count for a given background thread.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- misusing
realmResults.asObservable()
and misunderstanding Realm’s Rx support in general
Most Rx/Realm related questions on Stack Overflow have a tendency to be overly cryptic, because there is a fundamental misunderstanding on what Realm’s Rx support is supposed to do.
The point of realmResults.asObservable()
is to see the entire results as an Observable with no need for adding change listeners manually and be kept up to date (and receive notification in case the table is changed), on the UI thread.
If you flatMap a RealmResults, you’re bound to get confused eventually. I’ve been meaning to find a use-case in which it’s required, and I haven’t found it yet. Using switchMap makes sense, though.
In case you’re wondering, you’re supposed to open and close a Realm instance for your background threads even if you use it with Rx.
If you really need your RealmResults on a background thread as an Observable (never happened to me before), then use Observable.just(realm.where(…).find*()) (not async) instead of asObservable().
(Note: before Realm 2.0.3+, you should consider using your own RealmObservableFactory
, because a memory leak was found.)
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- attempting to paginate a
RealmResults<T>
or “limit the number of results in it” for no valid reason whatsoever
I’ve seen people on SO attempting to paginate a RealmResults, or “how do I do a limit query”. The first question asked is why are you even trying to do that.
Mostly because to limit a RealmResults, you simply have to not index it above your arbitrary threshold.
esseTo make it clear: RealmResults does NOT contain any elements. It contains the means to evaluate the results of the query. The element is obtained from the Realm only when you call realmResults.get(i), and only that single element is returned at a time. It’s like a Cursor, except it’s a List. Therefore, “limiting” it and “paginating” it doesn’t make sense. If you really need it, then limit your index.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
If you see findAll().sort(), it should typically be sort().findAll()
. Before Realm-Java 5.0.0, it used to be findAllSorted()
, so look for that.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- Not using @Index annotation on the fields you use in your query, even though you should
It makes your queries like 4x faster if not more. You should always use @Index if you use a field in a query.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- Opening a Realm instance on a background thread, obtaining the RealmResults, THEN opening the transaction, and then manipulating the results on the outside of the transaction
When you open a transaction, you’re writing directly into Realm’s latest version. Which means that your RealmResults should be obtained INSIDE the transaction, and NOT outside the transaction; so that you see the latest version at all times.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- Not using
RealmRecyclerViewAdapter
andRecyclerView
even though it’s been out for a while now
Also worth noting that their adapter (version 3.0.0) manages adding and removing a change listener that calls the adapter’s right adapter.notifyItem*
methods that allow animation on change, so literally all you need to do is supply the query you want to see the elements of on the screen and it just works and auto-updates when stuff happens.
ListViews and AsyncTasks make me feel pain. :/
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
This was mostly about common errors, but what’s Realm about anyways?
Realm solves a few problems that would otherwise take a lot of plumbing to handle yourself, which is what makes it pretty cool in the first place.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- Zero-copy query evaluation
Which is what I said earlier about RealmResults<T> not containing any elements. You only get proxy classes created when you call .get(i). The list you obtain is just a fancy cursor to the underlying database. Evaluating the RealmResults<T> itself is fairly cheap, and gives you easy access to all the data that qualifies for your conditions, without copying the entire set of data into memory.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- Allowing writes from multiple threads (only one transaction at a time) and automatic updates on the UI thread to show the latest version of data
Your background operation downloaded a new batch of cats on a background thread. Now you need to somehow show these new cats on top of the old cats in your RecyclerView. Let’s assume you hate AsyncTasks because you like rotating the screen.
You can send the newly inserted elements through an event bus, append them to your list, and then call adapter.notifyItemRangeInserted(...)
, but wouldn’t it be great if you just wrote the data in at one place, and the data was up-to-date everywhere else with no manual plumbing at all?
Yep, if you use RealmResults returned by your query and throw it in a RealmRecyclerViewAdapter (which doesn’t do much magic in the background by the way), any write you do on any thread will AUTOMATICALLY update your result set, always showing the latest data. Cool, huh?
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
- Listening for asynchronously obtained query results without much effort
If you’re not using a RealmRecyclerViewAdapter
, you can still listen for any changes made from any threads using a RealmChangeListener
added to your RealmResults
.
private Realm realm;
private RealmResults<Cat> cats;
private RealmChangeListener<...> realmChangeListener = cats -> {
adapter.setData(cats);
};@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
realm = Realm.getDefaultInstance();
cats = realm.where(Cat.class).findAllAsync();
cats.addChangeListener(realmChangeListener);
}@Override
protected void onDestroy() {
super.onDestroy();
cats.removeAllChangeListeners();
realm.close();
}
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
With that in mind, how should Realm be used?
- Do not use beginTransaction() and commitTransaction() manually. Use executeTransaction() (or executeTransactionAsync() on the UI thread, if needed).
- Use RealmRecyclerViewAdapter with findAllSortedAsync() method to offload the query completely from the UI thread, and automatically display any new elements committed from background threads (Realm’s adapter handles this for you with RealmChangeListener)
- No manual copying and no synchronization required whatsoever, you just define the query and how to bind your view holder with the Realm object, and you get a low-memory usage solution with auto-sync.
If you still don’t give up on copying the data in memory though, you can give a try to Monarchy.
— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —
Hopefully that helped you:
- get a bit of understanding on what Realm is, what Realm is for, and how to work with it
- how to identify bad posts like this one (overcomplicated Realm instance management, synchronous transactions on the UI thread, using begin/commit instead of execute, Realm instances that are NEVER closed, telling you to add initial data using a migration instead of
initialData(Realm.Transaction)
, using methods that no longer exist since 0.89.0… things like that).
I reworked the book example shown in that post above in this Github repository.
For a simple example that downloads external data, refer to this Github repository.
(by the way, with Medium’s new clapping feature, if you liked the article, you can actually press the “clap” button multiple times to express how much you liked it!)