Navigation

Notifications

Overview

Any modern app should be able to react when data changes, regardless of where that change originated. When a user adds a new item to a list, you may want to update the UI, show a notification, or log a message. When someone updates that item, you may want to change its visual state or fire off a network request. When someone deletes the item, you probably want to remove it from the UI. Realm’s notification system allows you to watch for and react to changes in your data, independent of the writes that caused the changes.

Realm emits three kinds of notifications:

Subscribe to Changes

To observe a Realm, collection, or object:

  1. Create a notification handler for the Realm, collection, or object notification.
  2. Add the notification handler to the Realm, collection, or object that you want to observe.
  3. Receive a notification token from the call to add the handler. Retain this token as long as you want to observe.
  4. When you are done observing, invalidate the token.

Realm Notifications

You can register a notification handler on an entire realm. Realm Database calls the notification handler whenever any write transaction involving that Realm is committed. The handler receives no information about the change.

This is useful when you want to know that there has been a change but do not care to know specifically what changed. For example, proof of concept apps often use this notification type and simply refresh the entire UI when anything changes. As the app becomes more sophisticated and performance-sensitive, the app developers shift to more granular notifications.

Example

Suppose you are writing a real-time collaborative app. To give the sense that your app is buzzing with collaborative activity, you want to have an indicator that lights up when any change is made. In that case, a realm notification handler would be a great way to drive the code that controls the indicator.

// Observe realm notifications. Keep a strong reference to the notification token
// or the observation will stop.
let token = realm.observe { notification, realm in
    // `notification` is an enum specifying what kind of notification was emitted.
    // See https://realm.io/docs/swift/latest/api/Classes/Realm.html#/s:10RealmSwift0A0C12NotificationO
    // for details.
    viewController.updateUI()
}

// Later, explicitly stop observing.
token.invalidate()
// Observe realm notifications. Keep a strong reference to the notification token
// or the observation will stop.
token = [realm addNotificationBlock:^(NSString *notification, RLMRealm *realm) {
    // Update UI...
}];

// Later, explicitly stop observing.
[token invalidate];

Collection Notifications

You can register a notification handler on a collection within a realm. The handler receives a description of changes since the last notification. Specifically, this description consists of three lists of indices:

  • The indices of the objects that were deleted.
  • The indices of the objects that were inserted.
  • The indices of the objects that were modified.

Order Matters

In collection notification handlers, always apply changes in the following order: deletions, insertions, then modifications. Handling insertions before deletions may result in unexpected behavior.

Realm Database emits an initial notification after retrieving the collection. After that, Realm Database delivers collection notifications asynchronously whenever a write transaction adds, changes, or removes objects in the collection.

Unlike Realm notifications, collection notifications contain detailed information about the change. This enables sophisticated and selective reactions to changes. Collection notifications provide all the information needed to manage a list or other view that represents the collection in the UI.

Example

The following code shows how to observe a collection for changes in order to update the UI.

class ViewController: UITableViewController {
    var notificationToken: NotificationToken? = nil

    override func viewDidLoad() {
        super.viewDidLoad()
        let realm = try! Realm()
        let results = realm.objects(Dog.self)

        // Observe collection notifications. Keep a strong
        // reference to the notification token or the
        // observation will stop.
        notificationToken = results.observe { [weak self] (changes: RealmCollectionChange) in
            guard let tableView = self?.tableView else { return }
            switch changes {
            case .initial:
                // Results are now populated and can be accessed without blocking the UI
                tableView.reloadData()
            case .update(_, let deletions, let insertions, let modifications):
                // Query results have changed, so apply them to the UITableView
                tableView.beginUpdates()
                // Always apply updates in the following order: deletions, insertions, then modifications.
                // Handling insertions before deletions may result in unexpected behavior.
                tableView.deleteRows(at: deletions.map({ IndexPath(row: $0, section: 0)}),
                                     with: .automatic)
                tableView.insertRows(at: insertions.map({ IndexPath(row: $0, section: 0) }),
                                     with: .automatic)
                tableView.reloadRows(at: modifications.map({ IndexPath(row: $0, section: 0) }),
                                     with: .automatic)
                tableView.endUpdates()
            case .error(let error):
                // An error occurred while opening the Realm file on the background worker thread
                fatalError("\(error)")
            }
        }
    }

    deinit {
        notificationToken?.invalidate()
    }
}
- (void)viewDidLoad {
    [super viewDidLoad];

    // Observe collection notifications. Retain the token to keep observing.
    __weak typeof(self) weakSelf = self;
    self.notificationToken = [[Dog allObjects] 
      addNotificationBlock:^(RLMResults<Dog *> *results, RLMCollectionChange *changes, NSError *error) {
        
        if (error) {
            NSLog(@"Failed to open Realm on background worker: %@", error);
            return;
        }

        UITableView *tableView = weakSelf.tableView;
        // Initial run of the query will pass nil for the change information
        if (!changes) {
            [tableView reloadData];
            return;
        }

        // Query results have changed, so apply them to the UITableView
        [tableView beginUpdates];
        [tableView deleteRowsAtIndexPaths:[changes deletionsInSection:0]
                         withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView insertRowsAtIndexPaths:[changes insertionsInSection:0]
                         withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView reloadRowsAtIndexPaths:[changes modificationsInSection:0]
                         withRowAnimation:UITableViewRowAnimationAutomatic];
        [tableView endUpdates];
    }];
}

- (void)dealloc {
    [self.notificationToken invalidate];
}

Object Notifications

You can register a notification handler on a specific object within a Realm. Realm Database notifies your handler:

  • When the object is deleted.
  • When any of the object’s properties change.

The handler receives information about what fields changed and whether the object was deleted.

Example

The following code shows how to open a default realm, create a new instance of a class, and observe that instance for changes.

// Define the dog class.
class Dog: Object {
    @objc dynamic var name = ""
}

var token: NotificationToken?

func example() {
    let dog = Dog()
    dog.name = "Max"

    // Open the default realm.
    let realm = try! Realm()
    try! realm.write {
        realm.add(dog)
    }
    // Observe object notifications. Keep a strong reference to the notification token
    // or the observation will stop. Invalidate the token when done observing.
    token = dog.observe { change in
        switch change {
        case .change(let properties):
            for property in properties {
                print("Property '\(property.name)' changed to '\(property.newValue!)'");
            }
        case .error(let error):
            print("An error occurred: \(error)")
        case .deleted:
            print("The object was deleted.")
        }
    }

    try! realm.write {
        dog.name = "Wolfie"
    }
}
// Define the dog.
@interface Dog : RLMObject
@property NSString *name; 
@end

@implementation Dog
@end

// ...elsewhere, in a ViewController implementation

RLMNotificationToken *notificationToken;

- (void)viewDidLoad {
    [super viewDidLoad];
    Dog *dog = [[Dog alloc] init];
    dog.name = @"Max";

    // Open the default realm.
    RLMRealm *realm = [RLMRealm defaultRealm];
    [realm beginWriteTransaction];
    [realm addObject:dog];
    [realm commitWriteTransaction];
    // Observe object notifications. Retain the notification token as long as you want to keep observing.
    notificationToken = [dog addNotificationBlock:^(BOOL deleted,
                                        NSArray<RLMPropertyChange *> *changes,
                                        NSError *error) {
        if (deleted) {
            NSLog(@"The object was deleted.");
        } else if (error) {
            NSLog(@"An error occurred: %@", error);
        } else {
            for (RLMPropertyChange *property in changes) {
                // List which properties have changed.
                NSLog(@"Property '%@' changed.", property.name);
            }
        }
    }];
    [realm beginWriteTransaction];
    dog.name = @"Wolfie";
    [realm commitWriteTransaction];
}

- (void)dealloc {
    [notificationToken invalidate];
}

Summary

  • Notifications allow you to watch for and react to changes on your objects, collections, and realms.
  • When you add a notification handler to an object, collection, or Realm that you wish to observe, you receive a token. Retain this token as long as you wish to keep observing.
  • Realm Database has three notification types: realm, collection, and object notifications. Realm notifications only tell you that something changed, while collection and object notifications allow for more granular observation.
←   Query Engine Threading  →