React to Changes - Kotlin SDK
On this page
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. Realm's notification system allows you to watch for and react to changes in your data, independent of the writes that caused the changes.
The frozen architecture of the Kotlin SDK makes notifications even more important. Because the Kotlin SDK doesn't have live objects that update automatically, you'll use notifications to keep your UI and data layer in sync.
You can subscribe to changes on the following events:
Example
About the Examples on This Page
The examples in this page use two Realm object types, Character
and
Fellowship
:
class Character(): RealmObject { var name: String = "" var species: String = "" var age: Int = 0 constructor(name: String, species: String, age: Int) : this() { this.name = name this.species = species this.age = age } } class Fellowship() : RealmObject { var name: String = "" var members: RealmList<Character> = realmListOf() constructor(name: String, members: RealmList<Character>) : this() { this.name = name this.members = members } }
The examples have this sample data:
val config = RealmConfiguration.Builder(setOf(Fellowship::class, Character::class)) .name(realmName) .build() val realm = Realm.open(config) val frodo = Character("Frodo", "Hobbit", 51) val samwise = Character("Samwise", "Hobbit", 39) val aragorn = Character("Aragorn", "Dúnedain", 87) val legolas = Character("Legolas", "Elf", 2931) val gimli = Character("Gimli", "Dwarf", 140) val gollum = Character("Gollum", "Hobbit", 589) val fellowshipOfTheRing = Fellowship( "Fellowship of the Ring", realmListOf(frodo, samwise, aragorn, legolas, gimli)) realm.writeBlocking{ this.copyToRealm(fellowshipOfTheRing) this.copyToRealm(gollum) // not in fellowship } realm.close()
Register a Query Change Listener
You can register a notification handler on any query within a Realm.
First, create a Kotlin Flow from the query with asFlow().
Next, use the collect()
method to handle events on that Flow. Events
of type UpdatedResults
record all changes to the objects matching the
query using the following properties:
Property | Type | Description |
insertions | IntArray | Indexes in the new collection which were added in this version. |
insertionRanges | Array<ListChangeSet.Range> | Ranges of indexes in the new collection which were added in this version. |
changes | IntArray | Indexes of the objects in the new collection which were modified in this version. |
changeRanges | Array<ListChangeSet.Range> | Ranges of indexes in the new collection which were modified in this version. |
deletions | IntArray | Indexes in the previous version of the collection which have been removed from this one. |
deletionRanges | Array<ListChangeSet.Range> | Ranges of indexes in the previous version of the collection which have been removed from this one. |
list | RealmResults<T as RealmObject> | Results collection being monitored for changes. |
// Listen for changes on whole collection val characters = realm.query(Character::class) // flow.collect() is blocking -- run it in a background context val job = CoroutineScope(Dispatchers.Default).launch { // create a Flow from that collection, then add a listener to the Flow val charactersFlow = characters.asFlow() val subscription = charactersFlow.collect { changes: ResultsChange<Character> -> when (changes) { // UpdatedResults means this change represents an update/insert/delete operation is UpdatedResults -> { changes.insertions // indexes of inserted objects changes.insertionRanges // ranges of inserted objects changes.changes // indexes of modified objects changes.changeRanges // ranges of modified objects changes.deletions // indexes of deleted objects changes.deletionRanges // ranges of deleted objects changes.list // the full collection of objects } else -> { // types other than UpdatedResults are not changes -- ignore them } } } } // Listen for changes on RealmResults val hobbits = realm.query(Character::class, "species == 'Hobbit'") val hobbitJob = CoroutineScope(Dispatchers.Default).launch { val hobbitsFlow = hobbits.asFlow() val hobbitsSubscription = hobbitsFlow.collect { changes: ResultsChange<Character> -> // ... all the same data as above } }
Register a RealmObject Change Listener
You can register a notification handler on a specific object within a realm.
Realm notifies your handler when any of the object's properties change.
To register a change listener on a single object, obtain a RealmSingleQuery
with realm.query.first()
. Generate a Flow from that query with asFlow().
The handler receives a SingleQueryChange
object that communicates
object changes using the following subtypes:
Subtype | Properties | Notes |
UpdatedObject | changedFields, obj | Pass a field name to isFieldChanged() to check if that field changed. |
DeletedObject | obj | Since obj always reflects the latest version of the object, it always returns a null value in this subtype. |
// query for the specific object you intend to listen to val frodo = realm.query(Character::class, "name == 'Frodo'").first() // flow.collect() is blocking -- run it in a background context val job = CoroutineScope(Dispatchers.Default).launch { val frodoFlow = frodo.asFlow() frodoFlow.collect { changes: SingleQueryChange<Character> -> when (changes) { is UpdatedObject -> { changes.changedFields // the changed properties changes.obj // the object in its newest state changes.isFieldChanged("name") // check if a specific field changed in value } is DeletedObject -> { // if the object has been deleted changes.obj // returns null for deleted objects -- always reflects newest state } } } }
Register a RealmList Change Listener
You can register a notification handler on a list of RealmObjects
within another RealmObject
.
Realm notifies your handler when any of the list items change.
First, create a Kotlin Flow from the list with asFlow().
Next, use the collect()
method to handle events on that Flow. Events
of type UpdatedList
record all changes to the list
using the following properties:
Property | Type | Description |
insertions | IntArray | Indexes in the new collection which were added in this version. |
insertionRanges | Array<ListChangeSet.Range> | Ranges of indexes in the new collection which were added in this version. |
changes | IntArray | Indexes of the objects in the new collection which were modified in this version. |
changeRanges | Array<ListChangeSet.Range> | Ranges of indexes in the new collection which were modified in this version. |
deletions | IntArray | Indexes in the previous version of the collection which have been removed from this one. |
deletionRanges | Array<ListChangeSet.Range> | Ranges of indexes in the previous version of the collection which have been removed from this one. |
list | RealmResults<T as RealmObject> | Results collection being monitored for changes. |
Important
You can only apply a change listener to a RealmList<T as RealmObject>
.
// query for the specific object you intend to listen to val fellowshipOfTheRing = realm.query(Fellowship::class, "name == 'Fellowship of the Ring'").first().find()!! val members = fellowshipOfTheRing.members // flow.collect() is blocking -- run it in a background context val job = CoroutineScope(Dispatchers.Default).launch { val membersFlow = members.asFlow() membersFlow.collect { changes: ListChange<Character> -> when (changes) { is UpdatedList -> { changes.insertions // indexes of inserted objects changes.insertionRanges // ranges of inserted objects changes.changes // indexes of modified objects changes.changeRanges // ranges of modified objects changes.deletions // indexes of deleted objects changes.deletionRanges // ranges of deleted objects changes.list // the full collection of objects } is DeletedList -> { // if the list was deleted } } } }
Unsubscribe a Change Listener
Unsubscribe from your change listener when you no longer want to receive notifications on updates to the data it's watching. To unsubscribe a change listener, cancel the enclosing coroutine.
// query for the specific object you intend to listen to val fellowshipOfTheRing = realm.query(Fellowship::class, "name == 'Fellowship of the Ring'").first().find()!! val members = fellowshipOfTheRing.members // flow.collect() is blocking -- run it in a background context val job = CoroutineScope(Dispatchers.Default).launch { val membersFlow = members.asFlow() membersFlow.collect { changes: ListChange<Character> -> // change listener stuff in here } } job.cancel() // cancel the coroutine containing the listener