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. Finally, when someone deletes the item, you probably want to remove it from the UI. MongoDB 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

Generally, this is how you 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.

class MyActivity : Activity() {
    private lateinit var realm: Realm
    private lateinit var realmListener: RealmChangeListener<Realm>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        realm = Realm.getDefaultInstance()
        realmListener = RealmChangeListener {
            // ... do something with the updates (UI, etc.) ...
        }
        // Observe realm notifications.
        realm.addChangeListener(realmListener)
    }

    override fun onDestroy() {
        super.onDestroy()
        // Remove the listener.
        realm.removeChangeListener(realmListener)
        // Close the Realm instance.
        realm.close()
    }
}
public class MyActivity extends Activity {
    private Realm realm;
    private RealmChangeListener realmListener;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        realm = Realm.getDefaultInstance();
        realmListener = new RealmChangeListener<Realm>() {
            @Override
            public void onChange(Realm realm) {
              // ... do something with the updates (UI, etc.) ...
            }
          };
        // Observe realm notifications.
        realm.addChangeListener(realmListener);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Remove the listener.
        realm.removeChangeListener(realmListener);
        // Close the Realm instance.
        realm.close();
    }
}

Collection Notifications

You can register a notification handler on a specific 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.

import io.realm.kotlin.*

// Implement a RecycleView Adapter that observes a collection for changes.
// This is just an example. Prefer RealmRecyclerViewAdapter for convenience.
class DogsRecyclerAdapter(
    realm: Realm // The constructor takes an open Realm.
): RecyclerView.Adapter<*>() {
    private var dogs: RealmResults<Dog>

    init {
        // Set up the collection notification handler.
        val changeListener = OrderedRealmCollectionChangeListener<RealmResults<Dog>> { dogs, changeSet ->
            // `null`  means the async query returns the first time.
            if (changeSet == null) {
                notifyDataSetChanged()
                return@OrderedRealmCollectionChangeListener
            }
            // For deletions, the adapter has to be notified in reverse order.
            val deletions = changeSet.getDeletionRanges()
            for (i in deletions.indices.reversed()) {
                val range = deletions[i]
                notifyItemRangeRemoved(range.startIndex, range.length)
            }

            val insertions = changeSet.getInsertionRanges()
            for (range in insertions) {
                notifyItemRangeInserted(range.startIndex, range.length)
            }

            val modifications = changeSet.getChangeRanges()
            for (range in modifications) {
                notifyItemRangeChanged(range.startIndex, range.length)
            }
        }
        dogs = realm.where<Dog>().findAll()
        // Observe collection notifications.
        dogs.addChangeListener(changeListener)
    }
    // ...
}
// Implement a RecycleView Adapter that observes a collection for changes.
// This is just an example. Prefer RealmRecyclerViewAdapter for convenience.
public class DogsRecyclerAdapter extends RecyclerView.Adapter {
    RealmResults<Dog> dogs;

    // The constructor takes an open realm.
    public DogsRecyclerAdapter(Realm realm) {
        // Set up the collection notification handler.
        OrderedRealmCollectionChangeListener<RealmResults<Dog>> changeListener = (dogs, changeSet) -> {
            // `null`  means the async query returns the first time.
            if (changeSet == null) {
                notifyDataSetChanged();
                return;
            }
            // For deletions, the adapter has to be notified in reverse order.
            OrderedCollectionChangeSet.Range[] deletions = changeSet.getDeletionRanges();
            for (int i = deletions.length - 1; i >= 0; i--) {
                OrderedCollectionChangeSet.Range range = deletions[i];
                notifyItemRangeRemoved(range.startIndex, range.length);
            }

            OrderedCollectionChangeSet.Range[] insertions = changeSet.getInsertionRanges();
            for (OrderedCollectionChangeSet.Range range : insertions) {
                notifyItemRangeInserted(range.startIndex, range.length);
            }

            OrderedCollectionChangeSet.Range[] modifications = changeSet.getChangeRanges();
            for (OrderedCollectionChangeSet.Range range : modifications) {
                notifyItemRangeChanged(range.startIndex, range.length);
            }
        };
        dogs = realm.where(Dog.class).findAll();
        // Observe collection notifications.
        dogs.addChangeListener(changeListener);
    }
    // ...
}

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.

// Dog.kt
open class Dog(
    var name: String = ""
): RealmObject()

// MyActivity.kt
class MyActivity : Activity() {
    private lateinit var realm: Realm
    private lateinit var listener: RealmObjectChangeListener<Dog>
    private lateinit var dog: Dog

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        realm = Realm.getDefaultInstance()
        // Create a dog in the realm.
        realm.executeTransaction { realm ->
            dog = realm.createObject(Dog::class.java)
            dog.name = "Max"
        }

        // Set up the listener.
        listener = RealmObjectChangeListener { dog, changeSet ->
            if (changeSet.isDeleted()) {
                // Dog was deleted
                return@RealmObjectChangeListener
            }

            for (fieldName in changeSet.getChangedFields()) {
                // "Field '$fieldName' changed."
            }
        }

        // Observe object notifications.
        dog.addChangeListener(listener)

        // Update the dog to see the effect.
        realm.executeTransaction { realm ->
            dog.name = "Wolfie" // -> "Field 'name' was changed."
        }
    }

    override fun onDestroy() {
        super.onDestroy()
        // Close the Realm instance.
        realm.close()
    }
}
// Dog.java
public class Dog extends RealmObject {
    @Required
    public String name;
}

// MyActivity.java
public class MyActivity extends Activity {
    private static final String TAG = "MyActivity";

    Realm realm;
    RealmObjectChangeListener<Dog> listener;
    Dog dog;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        realm = Realm.getDefaultInstance();
        // Create a dog in the realm.
        realm.executeTransaction(r -> {
            dog = realm.createObject(Dog.class);
            dog.name = "Max";
        });

        // Set up the listener.
        listener = (dog, changeSet) -> {
            if (changeSet.isDeleted()) {
                Log.i(TAG, "The dog was deleted");
                return;
            }

            for (String fieldName : changeSet.getChangedFields()) {
                Log.i(TAG, "Field '" + fieldName + "' changed.");
            }
        };

        // Observe object notifications.
        dog.addChangeListener(listener);

        // Update the dog to see the effect.
        realm.executeTransaction(r -> {
            dog.name = "Wolfie"; // -> "Field 'name' was changed."
        });
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        // Close the Realm instance.
        realm.close();
    }
}

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  →