Join us at MongoDB.local London on 7 May to unlock new possibilities for your data. Use WEB50 to save 50%.
Register now >
Docs Menu
Docs Home
/ /
Realm

React to Changes - Kotlin SDK

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:

  • Query on collection

  • Realm object

  • Realm collections (e.g. list)

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 {
@PrimaryKey
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 {
@PrimaryKey
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()

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

insertions

IntArray

Indexes in the new collection which were added in this version.

insertionRanges

Array<ListChangeSet.Range>

Rangos de índices en la nueva colección que se agregaron en esta versión.

changes

IntArray

Índices de los objetos en la nueva colección que se modificaron en esta versión.

changeRanges

Array<ListChangeSet.Range>

Rangos de índices en la nueva colección que fueron modificados en esta versión.

deletions

IntArray

Índices de la versión anterior de la colección que se han eliminado de esta.

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>

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
}
}

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

UpdatedObject

changedFields, obj

Pasa un nombre de campo a isFieldChanged() para comprobar si ese campo cambió.

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
}
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
}
}
}
}

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:

  • InitialList

  • UpdatedList

  • DeletedList

A ListChangeSet is an interface that models the changes that can occur in a list, and the properties include:

Propiedad

Tipo

Descripción

insertions

IntArray

Indexes in the new collection which were added in this version.

insertionRanges

Array<ListChangeSet.Range>

Rangos de índices en la nueva colección que se agregaron en esta versión.

changes

IntArray

Índices de los objetos en la nueva colección que se modificaron en esta versión.

changeRanges

Array<ListChangeSet.Range>

Rangos de índices en la nueva colección que fueron modificados en esta versión.

deletions

IntArray

Índices de la versión anterior de la colección que se han eliminado de esta.

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. This is provided when the event type is an UpdatedList.

RealmSet notifications implement the SetChange interface, which describes the possible changes that can occur in a RealmSet collection. These states are represented by subclasses:

  • InitialSet

  • UpdatedSet

  • DeletedSet

Un SetChangeSet es una interfaz que modela los cambios que pueden ocurrir en un conjunto, y las propiedades incluyen:

Propiedad

Tipo

Descripción

deletions

Int

The number of entries that have been deleted in this version of the collection.

insertions

Int

The number of entries that have been inserted in this version of the collection.

set

RealmSet<T>

El conjunto que se está supervisando para detectar cambios. Esto se proporciona cuando el tipo de evento es un UpdatedSet.

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:

  • InitialMap

  • UpdatedMap

  • DeletedMap

Un MapChangeSet es una interfaz que modela los cambios que pueden ocurrir en un mapa, y las propiedades incluyen:

Propiedad

Tipo

Descripción

deletions

Matriz<K>

The keys that have been deleted in this version of the map.

insertions

Matriz<K>

Las claves que se han insertado en esta versión del mapa.

changes

Matriz<K>

Las claves cuyos valores han cambiado en esta versión de la colección.

set

Mapa del reino<K, V>

The map being monitored for changes. This is provided when the event type is an UpdatedMap.

// 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
}
}
}
}

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.

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()
}

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()
}

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

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.

Volver

Bundle a Realm

En esta página