React a alterações - Kotlin SDK
Nesta página
- Registrar um ouvinte de alteração de query
- Registrar um ouvinte de alteração do RealmObject
- Registrar um ouvinte de alteração de coleção
- Registrar um ouvinte de alteração de caminho principal
- Observar caminhos-chave aninhados
- Observe os caminhos principais com curingas
- Cancelar a assinatura de um ouvinte de alterações
- Alterar limites de notificação
Qualquer aplicativo moderno deve ser capaz de React quando os dados mudam, independentemente da origem dessa alteração. Quando um usuário adiciona um novo item a uma lista, você pode querer atualizar a UI, mostrar uma notificação ou registrar uma mensagem. Quando alguém atualiza esse item, você pode querer alterar seu estado visual ou disparar uma solicitação de rede. Por fim, quando alguém exclui o item, você provavelmente quer removê-lo da UI. O sistema de notificação do Realm permite observar e React a alterações em seus dados, independentemente das gravações que causaram as alterações.
A arquitetura congelada do Kotlin SDK torna as notificações ainda mais importantes. Como o Kotlin SDK não tem objetos ativos que são atualizados automaticamente, você usará notificações para manter a interface do usuário e a camada de dados sincronizadas.
Você pode assinar as alterações nos seguintes eventos:
O SDK só fornece notificações para objetos aninhados em até quatro camadas de profundidade. Se você precisar React a alterações em objeto mais profundamente aninhados, registre um ouvinte de alteração de caminho principal. Para obter mais informações, consulte Registrar um ouvinte de alteração de caminho principal nesta página.
Você também pode React a alterações no estado de autenticação do usuário. Para obter mais informações, consulte Observar alterações de autenticação.
Exemplo
Sobre os exemplos nesta página
Os exemplos nesta página usam dois tipos de objeto de Realm, Character
e 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 } }
Os exemplos têm estes dados de amostra:
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 um ouvinte de alteração de query
Você pode registrar um manipulador de notificações em qualquer query dentro de um Realm. Primeiro, crie um fluxo Kotlin a partir da consulta com asFlow(). Em seguida, use o método collect()
para lidar com eventos nesse fluxo. Eventos do tipo UpdatedResults
registram todas as alterações nos objetos correspondentes à query usando as seguintes propriedades:
Propriedade | Tipo | Descrição |
insertions | IntArray | Índices na nova collection adicionados nesta versão. |
insertionRanges | Array<ListChangeSet.Range> | Faixas de índices na nova collection adicionadas nesta versão. |
changes | IntArray | Índices dos objetos na nova coleção que foram modificados nesta versão. |
changeRanges | Array<ListChangeSet.Range> | Faixas de índices na nova collection que foram modificadas nesta versão. |
deletions | IntArray | Índices na versão anterior da collection que foram removidos desta. |
deletionRanges | Array<ListChangeSet.Range> | Faixas de índices na versão anterior da collection que foram removidas desta. |
list | RealmResults<T como RealmObject> | A collection de resultados está sendo monitorada quanto a alterações. |
// 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 um ouvinte de alteração do RealmObject
Você pode registrar um manipulador de notificações em um objeto específico dentro de um Realm. O Realm notifica seu manipulador quando qualquer propriedade do objeto é alterada. Para registrar um ouvinte de alterações em um único objeto, obtenha uma RealmSingleQuery com realm.query.first()
. Gere um fluxo a partir dessa consulta com asFlow(). O manipulador recebe um objeto SingleQueryChange
que comunica as alterações de objeto usando os seguintes subtipos:
Subtipo | Propriedades | Notas |
UpdatedObject | changeFields, obj | Passe um nome de campo para isFieldChanged() para verificar se este campo foi alterado. |
DeletedObject | obj | Como o obj sempre reflete a versão mais recente do objeto, ele sempre retorna um valor nulo nesse 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 um ouvinte de alteração de coleção
Você pode registrar um manipulador de notificações em um RealmList
, RealmSet
ou RealmMap
. O Realm notifica seu manipulador quando algum dos itens da collection é alterado. Primeiro, crie um Kotlin a partir da collection com asFlow()
. Em seguida, use o método collect()
para lidar com eventos nesse fluxo. evento do tipo ListChange
, SetChange
ou MapChange
registram todas as alterações na collection.
RealmList
As notificações implementam a interface ListChange , que descreve as possíveis alterações que podem ocorrer em uma coleção RealmList. Esses estados são representados por subclasses:
InitialList
UpdatedList
DeletedList
Um ListChangeSet é uma interface que modela as alterações que podem ocorrer em uma lista, e as propriedades incluem:
Propriedade | Tipo | Descrição |
insertions | IntArray | Índices na nova collection adicionados nesta versão. |
insertionRanges | Array<ListChangeSet.Range> | Faixas de índices na nova collection adicionadas nesta versão. |
changes | IntArray | Índices dos objetos na nova coleção que foram modificados nesta versão. |
changeRanges | Array<ListChangeSet.Range> | Faixas de índices na nova collection que foram modificadas nesta versão. |
deletions | IntArray | Índices na versão anterior da collection que foram removidos desta. |
deletionRanges | Array<ListChangeSet.Range> | Faixas de índices na versão anterior da collection que foram removidas desta. |
list | RealmResults<T como RealmObject> | A collection de resultados está sendo monitorada quanto a alterações. Isso é fornecido quando o tipo de evento é UpdatedList . |
RealmSet
notificações implementam a interface SetChange , que descreve as possíveis alterações que podem ocorrer em uma coleção RealmSet. Esses estados são representados por subclasses:
InitialSet
UpdatedSet
DeletedSet
Um SetChangeSet é uma interface que modela as alterações que podem ocorrer em um conjunto, e as propriedades incluem:
Propriedade | Tipo | Descrição |
deletions | Int | O número de entradas que foram excluídas nesta versão da coleção. |
insertions | Int | O número de entradas que foram inseridas nesta versão da collection. |
set | RealmSet<T> | O conjunto que está sendo monitorado quanto a alterações. Isso é fornecido quando o tipo de evento é um UpdatedSet . |
RealmMap
As notificações implementam a interface MapChange , que descreve as possíveis alterações que podem ocorrer em uma coleção RealmMap. Esses estados são representados por subclasses:
InitialMap
UpdatedMap
DeletedMap
Um MapChangeSet é uma interface que modela as alterações que podem ocorrer em um mapa, e as propriedades incluem:
Propriedade | Tipo | Descrição |
deletions | Array<K> | As chaves que foram excluídas nesta versão do mapa. |
insertions | Array<K> | As chaves que foram inseridas nesta versão do mapa. |
changes | Array<K> | As chaves cujos valores foram alterados nesta versão da collection. |
set | RealmMap<K, V> | O mapa sendo monitorado quanto a alterações. Isso é fornecido quando o tipo de evento é um 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 } } } }
Registrar um ouvinte de alteração de caminho principal
Novidade na versão 1.13.0.
Ao registrar um manipulador de notificações, você pode passar uma lista opcional de nomes de propriedades de string para especificar o caminho de chave ou caminhos de chave a serem observados.
Quando você especifica caminhos-chave, somente as alterações nesses caminhos-chave trigger bloqueios de notificação. Quaisquer outras alterações não bloqueia notificações de trigger.
No exemplo a seguir, registramos um ouvinte de alteração de caminho principal para a propriedade 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() }
Observação
Vários tokens de notificação no mesmo objeto que filtram exclusivamente caminhos de chave separados não filtram. Se uma alteração de caminho-chavefor satisfeita para um token de notificação, todos os bloqueios de token de notificação para esse objeto serão executados.
Observar caminhos-chave aninhados
Você pode usar a notação de pontos para observar caminhos de chaves aninhados. Por padrão, o SDK só relata notificações para objetos aninhados em até quatro camadas de profundidade. Observe um caminho de chave específico se você precisar observar alterações em objetos aninhados mais profundamente.
No exemplo a seguir, observamos as atualizações da propriedade aninhada members.age
. Observe que o SDK relata a alteração na propriedade de nível superior members
, embora estejamos atentos às alterações em uma propriedade aninhada:
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 os caminhos principais com curingas
Você pode usar curingas (*
) para observar alterações em todos os caminhos-chave no nível do curinga.
No exemplo a seguir, usamos a observação curinga para alterações em quaisquer propriedades aninhadas em um nível de profundidade na propriedade members
. Com base no modelo usado neste exemplo, esse ouvinte de alterações relataria alterações nas propriedades age
, species
ou name
de qualquer membro. Observe que o SDK relata a alteração na propriedade members
de nível superior, embora estejamos atentos às alterações em uma propriedade aninhada:
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 a assinatura de um ouvinte de alterações
Cancele a assinatura do seu ouvinte de alterações quando não quiser mais receber notificações sobre atualizações nos dados que ele está assistindo. Para cancelar a assinatura de um ouvinte de alterações, cancele o coroutine de encerramento.
// 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
Alterar limites de notificação
Alterações em documentos aninhados com mais de quatro níveis abaixo não acionam notificações de alterações.
Se você tiver uma estrutura de dados em que precise escutar as alterações em cinco níveis para baixo ou mais profundamente, as soluções alternativas incluem:
Refatore o esquema para reduzir o aninhamento.
Adicione algo como "pressione para atualizar" para permitir que os usuários atualizem os dados manualmente.