Cualquier aplicación moderna debería ser capaz de reaccionar cuando los datos cambien, sin importar de dónde provengan los cambios. Cuando un usuario agrega un nuevo ítem a una lista, puedes querer actualizar la Interfaz de Usuario, mostrar una notificación o registrar un mensaje. Cuando alguien actualiza ese elemento, es posible que se quiera cambiar su estado visual o ejecutar una solicitud de red. Por último, cuando alguien borra el elemento, probablemente desees removerlo de la Interfaz de Usuario. El sistema de notificaciones de Realm te permite observar y reaccionar a los cambios en tus datos, de manera independiente de las operaciones de guardado que causaron los cambios.
La arquitectura fija del SDK de Kotlin hace que las notificaciones sean aún más importantes. Dado que el SDK de Kotlin no tiene objetos activos que se actualicen automáticamente, usarás notificaciones para mantener sincronizadas la interfaz de usuario y la capa de datos.
You can subscribe to changes on the following events:
El SDK solo proporciona notificaciones para objetos anidados hasta en cuatro capas. Si necesita reaccionar a cambios en objetos anidados más profundos, registre un detector de cambios de ruta de clave. Para obtener más información, consulte "Registrar un detector de cambios de ruta de clave" en esta página.
También puedes reaccionar a los cambios en el estado de autenticación del usuario. Para obtener más información, consulta Observar cambios de autenticación.
Ejemplo
About the Examples on This Page
Los ejemplos de esta página utilizan dos tipos de objetos Realm, Character y 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
Puedes registrar un controlador de notificaciones en cualquier consulta dentro de un dominio. Primero, crea un flujo Kotlin a partir de la consulta con asFlow(). A continuación, usa el collect() método para gestionar los eventos de ese flujo. Los eventos de tipo UpdatedResults registran todos los cambios en los objetos que coinciden con la consulta mediante las siguientes propiedades:
Propiedad | Tipo | Descripción |
| IntArray | Indexes in the new collection which were added in this version. |
| Array<ListChangeSet.Range> | Rangos de índices en la nueva colección que se agregaron en esta versión. |
| IntArray | Índices de los objetos en la nueva colección que se modificaron en esta versión. |
| Array<ListChangeSet.Range> | Rangos de índices en la nueva colección que fueron modificados en esta versión. |
| IntArray | Índices de la versión anterior de la colección que se han eliminado de esta. |
| Array<ListChangeSet.Range> | Ranges of indexes in the previous version of the collection which have been removed from this one. |
| RealmResults<T as RealmObject> | Se monitorea la recopilación de resultados para detectar cambios. |
// 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
Puedes registrar un gestor de notificaciones en un objeto específico dentro de un Realm. Realm notifica a tu controlador cuando cualquiera de las propiedades del objeto cambia. Para registrar un listener de cambios en un solo objeto, obtén un RealmSingleQuery con realm.query.first(). Genera un Flow a partir de esa query con asFlow(). El handler recibe un objeto SingleQueryChange que comunica los cambios del objeto utilizando los siguientes subtipos:
subtipo | Propiedades | notas |
| changedFields, obj | Pasa un nombre de campo a |
| 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 } is InitialObject -> { // Initial event observed on a RealmObject or EmbeddedRealmObject flow. // It contains a reference to the starting object state. changes.obj } is PendingObject -> { // Describes the initial state where a query result does not contain any elements. changes.obj } } } }
Registrar un oyente de cambios de colección
You can register a notification handler on a RealmList, RealmSet, or RealmMap. Realm notifies your handler when any of the collection items change. First, create a Kotlin Flow from the collection with asFlow(). Next, use the collect() method to handle events on that Flow. Events of type ListChange, SetChange, or MapChange record all changes to the collection.
RealmList las notificaciones implementan la interfaz ListChange, que describe los cambios posibles que pueden ocurrir en una colección RealmList. Estos estados están representados por subclases:
InitialListUpdatedListDeletedList
A ListChangeSet is an interface that models the changes that can occur in a list, and the properties include:
Propiedad | Tipo | Descripción |
| IntArray | Indexes in the new collection which were added in this version. |
| Array<ListChangeSet.Range> | Rangos de índices en la nueva colección que se agregaron en esta versión. |
| IntArray | Índices de los objetos en la nueva colección que se modificaron en esta versión. |
| Array<ListChangeSet.Range> | Rangos de índices en la nueva colección que fueron modificados en esta versión. |
| IntArray | Índices de la versión anterior de la colección que se han eliminado de esta. |
| Array<ListChangeSet.Range> | Ranges of indexes in the previous version of the collection which have been removed from this one. |
| RealmResults<T as RealmObject> | Results collection being monitored for changes. This is provided when the event type is an |
RealmSet notifications implement the SetChange interface, which describes the possible changes that can occur in a RealmSet collection. These states are represented by subclasses:
InitialSetUpdatedSetDeletedSet
Un SetChangeSet es una interfaz que modela los cambios que pueden ocurrir en un conjunto, y las propiedades incluyen:
Propiedad | Tipo | Descripción |
| Int | The number of entries that have been deleted in this version of the collection. |
| Int | The number of entries that have been inserted in this version of the collection. |
| RealmSet<T> | El conjunto que se está supervisando para detectar cambios. Esto se proporciona cuando el tipo de evento es un |
RealmMap Las notificaciones implementan la interfaz MapChange, que describe los posibles cambios que pueden ocurrir en una colección RealmMap. Estos estados se representan mediante subclases:
InitialMapUpdatedMapDeletedMap
Un MapChangeSet es una interfaz que modela los cambios que pueden ocurrir en un mapa, y las propiedades incluyen:
Propiedad | Tipo | Descripción |
| Matriz<K> | The keys that have been deleted in this version of the map. |
| Matriz<K> | Las claves que se han insertado en esta versión del mapa. |
| Matriz<K> | Las claves cuyos valores han cambiado en esta versión de la colección. |
| Mapa del reino<K, V> | The map being monitored for changes. This is provided when the event type is an |
// 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 } is InitialList -> { // Initial event observed on a RealmList flow. It contains a reference // to the starting list state. changes.list } } } }
Register a Key Path Change Listener
Novedades en la versión 1.13.0.
When you register a notification handler, you can pass an optional list of string property names to specify the key path or key paths to watch.
Al especificar rutas de clave, solo los cambios en ellas activan bloques de notificación. Los demás cambios no activan bloques de notificación.
In the following example, we register a key path change listener for the age property:
runBlocking { // Query for the specific object you intend to listen to. val frodoQuery = realm.query(Character::class, "name == 'Frodo'").first() val observer = async { val frodoFlow = frodoQuery.asFlow(listOf("age")) frodoFlow.collect { changes: SingleQueryChange<Character> -> // Change listener stuff in here. } } // Changing a property whose key path you're not observing does not trigger a notification. realm.writeBlocking { findLatest(frodoObject)!!.species = "Ring Bearer" } // Changing a property whose key path you are observing triggers a notification. realm.writeBlocking { findLatest(frodoObject)!!.age = 52 } // For this example, we send the object change to a Channel where we can verify the // changes we expect. In your application code, you might use the notification to // update the UI or take some other action based on your business logic. channel.receiveOrFail().let { objChange -> assertIs<UpdatedObject<*>>(objChange) assertEquals(1, objChange.changedFields.size) // Because we are observing only the `age` property, the change to // the `species` property does not trigger a notification. // The first notification we receive is a change to the `age` property. assertEquals("age", objChange.changedFields.first()) } observer.cancel() channel.close() }
Nota
Varios tokens de notificación en el mismo objeto que filtran por rutas de clave independientes no filtran de forma exclusiva. Si se cumple un cambio de ruta de clave para un token de notificación, se ejecutarán todos los bloques de tokens de notificación de ese objeto.
Observe Nested Key Paths
You can use dot notation to observe nested key paths. By default, the SDK only reports notifications for objects nested up to four layers deep. Observe a specific key path if you need to observe changes on more deeply nested objects.
En el siguiente ejemplo, buscamos actualizaciones en la propiedad anidada members.age. Tenga en cuenta que el SDK informa del cambio en la propiedad de nivel superior members, aunque estemos buscando cambios en una propiedad anidada:
runBlocking { // Query for the specific object you intend to listen to. val fellowshipQuery = realm.query(Fellowship::class).first() val observer = async { val fellowshipFlow = fellowshipQuery.asFlow(listOf("members.age")) fellowshipFlow.collect { changes: SingleQueryChange<Fellowship> -> // Change listener stuff in here. } } // Changing a property whose nested key path you are observing triggers a notification. val fellowship = fellowshipQuery.find()!! realm.writeBlocking { findLatest(fellowship)!!.members[0].age = 52 } // For this example, we send the object change to a Channel where we can verify the // changes we expect. In your application code, you might use the notification to // update the UI or take some other action based on your business logic. channel.receiveOrFail().let { objChange -> assertIs<UpdatedObject<*>>(objChange) assertEquals(1, objChange.changedFields.size) // While you can watch for updates to a nested property, the notification // only reports the change on the top-level property. In this case, there // was a change to one of the elements in the `members` property, so `members` // is what the notification reports - not `age`. assertEquals("members", objChange.changedFields.first()) } observer.cancel() channel.close() }
Observe rutas clave con comodines
Puedes usar comodines (*) para observar cambios en todas las rutas clave al nivel del comodín.
En el siguiente ejemplo, utilizamos el comodín de watch para detectar cambios en cualquier propiedad anidada a un nivel de profundidad dentro de la propiedad members. Con base en el modelo utilizado en este ejemplo, este listener de cambios reportaría cambios en cualquier propiedad de age, species o name de los nodos. Ten en cuenta que el SDK reporta el cambio en la propiedad members de primer nivel, aunque estamos monitoreando cambios en una propiedad anidada:
runBlocking { // Query for the specific object you intend to listen to. val fellowshipQuery = realm.query(Fellowship::class).first() val observer = async { // Use a wildcard to observe changes to any key path at the level of the wildcard. val fellowshipFlow = fellowshipQuery.asFlow(listOf("members.*")) fellowshipFlow.collect { changes: SingleQueryChange<Fellowship> -> // Change listener stuff in here. } } // Changing any property at the level of the key path wild card triggers a notification. val fellowship = fellowshipQuery.find()!! realm.writeBlocking { findLatest(fellowship)!!.members[0].age = 52 } // For this example, we send the object change to a Channel where we can verify the // changes we expect. In your application code, you might use the notification to // update the UI or take some other action based on your business logic. channel.receiveOrFail().let { objChange -> assertIs<UpdatedObject<*>>(objChange) assertEquals(1, objChange.changedFields.size) // While you can watch for updates to a nested property, the notification // only reports the change on the top-level property. In this case, there // was a change to one of the elements in the `members` property, so `members` // is what the notification reports - not `age`. assertEquals("members", objChange.changedFields.first()) } observer.cancel() channel.close() }
Unsubscribe a Change Listener
Cancele la suscripción a su detector de cambios cuando ya no desee recibir notificaciones sobre las actualizaciones de los datos que supervisa. Para cancelar la suscripción a un detectorde cambios, cancele la corrutina que lo contiene.
// 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
Change Notification Limits
Changes in nested documents deeper than four levels down do not trigger change notifications.
If you have a data structure where you need to listen for changes five levels down or deeper, workarounds include:
Refactorice el esquema para reducir la anidación.
Add something like "push-to-refresh" to enable users to manually refresh data.