Cualquier aplicación moderna debería poder reaccionar ante cambios en los datos, independientemente de su origen. Cuando un usuario añade un elemento a una lista, es posible que quieras actualizar la interfaz de usuario, mostrar una notificación o registrar un mensaje. Cuando alguien actualiza ese elemento, es posible que quieras cambiar su estado visual o enviar una solicitud de red. Finalmente, cuando alguien elimina el elemento, probablemente quieras eliminarlo de la interfaz de usuario. El sistema de notificaciones de Realm te permite observar y reaccionar ante los cambios en tus datos, independientemente de las escrituras que los provocaron.
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.
Puedes suscribirte a los cambios en los siguientes eventos:
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
Acerca de los ejemplos de esta página
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 } }
Los ejemplos tienen estos datos de muestra:
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()
Registrar un oyente de cambio de consulta
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 | Índices de la nueva colección que se agregaron en esta versión. |
| Matriz<ListChangeSet.Range> | Rangos de índices en la nueva colección que se agregaron en esta versión. |
| IntArray | Índices de los objetos de la nueva colección que fueron modificados en esta versión. |
| Matriz<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. |
| Matriz<ListChangeSet.Range> | Rangos de índices de la versión anterior de la colección que se han eliminado de esta. |
| Resultados del reino<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 } }
Registrar un detector de cambios de RealmObject
Puede registrar un controlador de notificaciones en un objeto específico dentro de un dominio. Realm notifica a su controlador cuando cambia alguna de las propiedades del objeto. Para registrar un detector de cambios en un solo objeto, obtenga una consulta RealmSingleQuery realm.query.first()con. Genere un flujo a partir de esa consulta con asFlow(). El controlador recibe un SingleQueryChange objeto que comunica los cambios del objeto mediante los siguientes subtipos:
subtipo | Propiedades | notas |
| campos modificados, obj | Pase un nombre de campo a |
| obj | Dado que obj siempre refleja la última versión del objeto, siempre devuelve un valor nulo en este subtipo. |
// 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
Puedes registrar un controlador de notificaciones en RealmList, RealmSet o RealmMap. Realm notifica a tu controlador cuando cambia cualquier elemento de la colección. Primero, crea un flujo de Kotlin a partir de la colección con asFlow(). A continuación, usa el método collect() para gestionar los eventos de ese flujo. Los eventos de tipo ListChange, SetChange o MapChange registran todos los cambios en la colección.
RealmList Las notificaciones implementan la interfaz ListChange, que describe los posibles cambios que pueden ocurrir en una colección RealmList. Estos estados se representan mediante subclases:
InitialListUpdatedListDeletedList
Un ListChangeSet es una interfaz que modela los cambios que pueden ocurrir en una lista, y las propiedades incluyen:
Propiedad | Tipo | Descripción |
| IntArray | Índices de la nueva colección que se agregaron en esta versión. |
| Matriz<ListChangeSet.Range> | Rangos de índices en la nueva colección que se agregaron en esta versión. |
| IntArray | Índices de los objetos de la nueva colección que fueron modificados en esta versión. |
| Matriz<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. |
| Matriz<ListChangeSet.Range> | Rangos de índices de la versión anterior de la colección que se han eliminado de esta. |
| Resultados del reino<T as RealmObject> | Se está monitorizando la recopilación de resultados para detectar cambios. Esto se proporciona cuando el tipo de evento es |
RealmSet Las notificaciones implementan la interfaz SetChange, que describe los posibles cambios que pueden ocurrir en una colección RealmSet. Estos estados se representan mediante subclases:
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 | El número de entradas que se han eliminado en esta versión de la colección. |
| Int | El número de entradas que se han insertado en esta versión de la colección. |
| RealmSet<T> | El conjunto que se está monitoreando para detectar cambios. Esto se proporciona cuando el tipo de evento es |
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> | Las claves que se han eliminado en esta versión del mapa. |
| 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> | El mapa que se está monitoreando para detectar cambios. Esto se proporciona cuando el tipo de evento es |
// 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 } } } }
Registrar un detector de cambios de ruta de clave
Nuevo en la versión 1.13.0.
Cuando registra un controlador de notificaciones, puede pasar una lista opcional de nombres de propiedades de cadena para especificar la ruta o rutas de clave a observar.
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.
En el siguiente ejemplo, registramos un detector de cambio de ruta de clave para la propiedad age:
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.
Observar rutas de claves anidadas
Puede usar la notación de puntos para observar rutas de claves anidadas. De forma predeterminada, el SDK solo notifica objetos anidados hasta en cuatro capas. Observe una ruta de clave específica si necesita observar cambios en objetos anidados con mayor profundidad.
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 para detectar cambios en cualquier propiedad anidada a un nivel de profundidad dentro de la propiedad members. Según el modelo utilizado en este ejemplo, este detector de cambios informaría sobre los cambios en las propiedades age, species o name de cualquier miembro. Tenga en cuenta que el SDK informa sobre el cambio en la propiedad members de nivel superior, aunque estemos observando 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() }
Cancelar la suscripción de un oyente de cambios
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
Límites de notificación de cambios
Los cambios en documentos anidados a más de cuatro niveles de profundidad no activan notificaciones de cambio.
Si tiene una estructura de datos en la que necesita escuchar cambios cinco niveles más abajo o más profundos, las soluciones alternativas incluyen:
Refactorice el esquema para reducir la anidación.
Agregue algo como "push-to-refresh" para permitir que los usuarios actualicen los datos manualmente.