This is the fifth post in a multi-part series on the Realm Mobile Database; it highlights the Realm SDK features that allow developers to provide partial UI updates based on fine-grained notifications. If you missed the first post on our custom engine, the second post on the benefits of some of the Realm API types, and the third post on sharing code across platforms, or the fourth post on good architecture, we recommend taking few minutes to read them! Also make sure to check out the final and sixth post, which shows how the features in the previous parts come together to make a database for modern mobile apps.
“Classic” persistence layers allow you to store data and then later retrieve that data. The usual approach to building your application then revolves around the notions of creating some data entities, pushing them to storage, and then deciding later when you need some of them back & retrieving them from the storage.
However, since it’s highly unlikely your app always reads and writes data from within the same class, you end up having to come up with all kind of clever mechanisms to communicate between your classes. They have to tell each other when there are changes, what’s been written, and if should they refresh the data they’ve fetched previously.
Often, in a complex app, developers will end up using a number of different messaging mechanisms to allow unrelated classes to let each other know that data has been added or updated. These might include notification center, callback blocks, setters, injection of classes, KVO, various delegate protocols, and others.
The cause of these problems is that the data is static. It’s frozen in time at the precise moment when it was retrieved from the disk; as soon as it’s fetched, it can be considered outdated. Put another way, it might be modified by another class just a split second later, and is therefore to always be considered “old”.
The root of the problem is that what your class gets back from the persistence layer is a static, frozen-in-time piece of data. The only way to make sure the data is “fresh” is to fetch it again and again every time you use it.
A Database Full of Live Objects
In the first post in this series, which speaks about Realm’s custom database engine, we already discussed the fact that Realm is not an iteration on existing technologies, but features its own custom database engine instead.
Realm does not use SQL, and doesn’t need to convert between a storage-suitable format and format usable from code. Additionally, the Realm SDK does not copy data into memory until the moment it’s being actively used.
That’s why we say that Realm is a Live Object Database — the data you access is always current and “fresh”. There is no notion of “fetching” data from disk, which needs to be constantly reloaded. There is no need to duplicate your data model from disk into memory.
This completely changes how developers write their controller classes. There is no need to revolve a controller’s life around UI updates, reads and writes, pushing and pulling data chunks.
In fact, reading and writing can happen in different spots in the project, on different threads, from different processes, or, when using the Realm Mobile Platform, from anywhere in the world. Each class in the app can focus on the business logic and forget about the notion of outdated or cached data, since Realm objects are always up to date.
And of course it’s not just single objects from your Realm database that are always fresh, but it’s also your collections: result sets and lists! How great is that? 👏
For example, once you create a Results
object, it always reflects the current result set from disk. Give it a try:
let people = realm.objects(Person.self)
print(people.count) // outputs: "0"
try! realm.write {
let marin = Person()
realm.add(marin)
}
print(people.count) // outputs: "1"
You don’t have to call a reload
or a refresh
method; the results set always gives you the latest data from the Realm Mobile Database.
Fine-Grained Notifications
Even though Realm’s Results
and List
classes always give you the latest, freshest data, your UI doesn’t know about the changes that have occurred.
Thankfully, the Realm Mobile Database also takes care of that. You simply “subscribe” to change notifications on Results
or a List
instance. Realm will not only let you know when a change has occurred, but also the precise indexes of the objects that have been inserted, updated, or deleted.
In this way, not only you always do have the freshest data at hand, but also know exactly when and what changed:
The good news is that once you have the precise indexes of the collection objects that changed, it’s really easy to update the corresponding parts of your app’s UI.
Of course you are welcome to simply call reloadData()
and reload your table’s rows or collection’s cells, but creating custom animations specifically for the modified rows isn’t that much more code.
If we consider one more time the companion project for this series, we will see that the complete code to update an iOS table view from a notification is quite simple. Peek inside MasterViewController.swift:
switch changes {
case .initial:
self.tableView.reloadData()
case .update(_, let deletions, let insertions, let updates):
self.tableView.beginUpdates()
self.tableView.insertRows(at: insertions.map {IndexPath(row: $0, section: 0)}, with: .automatic)
self.tableView.reloadRows(at: updates.map {IndexPath(row: $0, section: 0)}, with: .automatic)
self.tableView.deleteRows(at: deletions.map {IndexPath(row: $0, section: 0)}, with: .automatic)
self.tableView.endUpdates()
default: break
}
The notification changes are packed in three convenient cases:
.initial(let results)
- When the result set or list has just started to be observed. Depending on your code, this is most likely happening while a view controller is being pushed on screen. Most often you don’t need animations here - a simplereloadData()
suffices..update(let results, let deletions, let insertions, let updates)
- A notification about a data change of the observed collection. You have the precise collection indexes and the types of the changes..error(let error)
- An error has happened during observing the collection. 😵
In the companion project, we left this code in the view controller class to showcase how few lines of code you need to implement animated table changes (also because the project includes versions of MasterViewController
for tvOS, macOS, etc.). In your code however, you can easily extract this code in a UITableView
extension for easier reuse across different view controllers:
extension UITableView {
func applyChanges<T>(changes: RealmCollectionChange<T>) {
switch changes {
case .initial: reloadData()
case .update(_, let deletions, let insertions, let updates):
let fromRow = {(row: Int) in
return IndexPath(row: row, section: 0)}
beginUpdates()
deleteRows(at: deletions.map(fromRow), with: .automatic)
insertRows(at: insertions.map(fromRow), with: .automatic)
reloadRows(at: updates.map(fromRow), with: .none)
endUpdates()
default: break
}
}
}
Using this method will “wire” any of your table views to a Realm collection:
let token = myResults.addNotificationToken(tableView.applyChanges)
It is no coincidence that the UITableView
and UICollectionView
APIs match so perfectly with the Realm SDK’s. Realm is no generic, multi-purpose database — its SDK has been specifically designed to match the needs programmers have while developing mobile applications.
Memory-Efficient Change Updates
Realm has the power to give you back that change set without having to produce the diff between the last collection and the current collection. Third-party libraries would do exactly that: They would keep (in memory!) both the “old” data and the current data, and compare them object by object to produce the change set.
This bloats the application’s memory by keeping twice as much data around. Besides being a memory hog, that approach is also extremely slow.
Since the Realm SDK doesn’t have to do that, it lets you avoid several performance drains: duplicating your data into memory, producing change diffs, and switching threads to do it all. You get the changed indexes right away and can read the new data as usual. Done!
Interface-Driven Writes
Last but not least, Realm has one more convenient feature when it comes to notification info: It allows you to distinguish between write operations that are initiated as a consequence of a user interacting with the UI, and the ones happening otherwise.
In terms of app logic, these two cases frequently require different approaches. Let’s quickly have a look!
1. Interface driven - A user swipes left on a table cell, and taps on “Delete”. This removes the row from the table view and calls the relevant table delegate method. In that method, you use Realm to delete the corresponding object. But of course you don’t need to be notified about the change, since your UI is already up to date.
Skipping notifications allows you to update your UI instantly, instead of reacting to the data layer’s change notification (which is delivered asynchronously). When you call commitWrites
, simply add the withoutNotifying
parameter and include the list of tokens to be “kept in the dark” about this write transaction:
func viewDidLoad() {
self.messages = realm.objects(Message.self)
self.token = self.messages.addNotificationToken(tableView.applyChanges)
}
func tableView(_ tableView: UITableView, commit editingStyle: UITableViewCellEditingStyle, forRowAt indexPath: IndexPath) {
if (editingStyle == .delete) {
realm.beginWrite()
messages.removeAtIndex(indexPath.row)
realm.commitWrite(withoutNotifying: [self.token])
tableView.deleteRows(at: [indexPath], with: .automatic)
}
}
2. Non-interface initiated - A networking class fetches JSON on a background thread and writes the new objects to your Realm database. In this case you do want to be notified about that change, so you can update your table view on screen.
In this case you subscribe for notifications and do your writes as usual.
We Hope You Like What You’re Seeing!
In this post, you learned about one of the best aspects of the Realm SDK - its fine-grained notifications, which you can observe to provide partial updates to your application’s UI.
As always, if you have few extra minutes, you are welcome to check out the Realm Mobile Database:
- Download the database (it’s free!)
- Or read the rest of this blog series
In the next installment in this series, we’re going to do an overview of everything we covered in this series and talk about how the special abilities of the Realm Mobile Database change application development.
See you next time! 👋
Receive news and updates from Realm straight to your inbox