React - Kotlin SDK
任何现代应用程序都应该能够在数据发生变化时React,无论更改源自何处。当用户向列表添加新项目时,您可能希望更新用户界面、显示通知或记录消息。 当有人更新该项目时,您可能希望更改其视觉状态或发出网络请求。 最后,当有人删除该项目时,您可能希望将其从用户界面中删除。 Realm 的通知系统允许您监视数据更改并做出 React,而与导致更改的写入无关。
Kotlin SDK 的冻结架构使通知变得更加重要。 由于 Kotlin SDK 没有自动更新的活动对象,因此您将使用通知来保持用户界面和数据层同步。
您可以订阅以下事件的变更:
SDK 仅为嵌套深度不超过四层的对象提供通知。 如果需要对嵌套更深的React中的更改做出对象,请注册键路径更改侦听器。有关更多信息,请参阅本页上的注册键路径更改侦听器。
您还可以对用户身份验证状态的变化做出反应。 有关更多信息,请参阅观察身份验证更改。
例子
关于本页中的示例
本页中的示例使用了两种 Realm 对象类型:Character
和 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 } }
这些示例包含以下示例数据:
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()
注册查询变更监听器
您可以为 Realm 中的任何查询注册通知处理程序。 首先,使用asFlow()从查询中创建 Kotlin 流。 接下来,使用collect()
方法处理该流上的事件。 UpdatedResults
类型的事件使用以下属性记录与查询匹配的对象的所有更改:
属性 | 类型 | 说明 |
insertions | IntArray | 此版本中添加的新集合的索引。 |
insertionRanges | Array<ListChangeSet.Range> | 此版本中添加的新collection中的索引范围。 |
changes | IntArray | 此版本中修改的新集合中对象的索引。 |
changeRanges | Array<ListChangeSet.Range> | 在此版本中修改的新集合中的索引范围。 |
deletions | IntArray | 先前版本的集合索引已从本版本中删除。 |
deletionRanges | Array<ListChangeSet.Range> | 已从本版本中删除的上一个版本的collection中的索引范围。 |
list | RealmResults<T 作为 RealmObject> | 正受到变更监控的结果集合。 |
// 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 } }
注册 RealmObject 变更监听器
您可以在域中的特定对象上注册通知处理程序。 当对象的任何属性发生变化时, Realm会通知您的处理程序。 要在单个对象上注册变更侦听器,请使用realm.query.first()
获取RealmSingleQuery 。 使用asFlow()从该查询生成流。 处理程序接收一个SingleQueryChange
对象,该对象使用以下子类型传达对象更改:
子类型 | 属性 | 注意 |
UpdatedObject | ChangedFields , obj | 将字段名称传递给 isFieldChanged() 以检查该字段是否已更改。 |
DeletedObject | obj | 由于obj始终反映对象的最新版本,因此它始终在此子类型中返回 null 值。 |
// 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 } } } }
注册集合变更监听器
您可以在RealmList
、 RealmSet
或RealmMap
上注册通知处理程序。 当任何collection项发生变化时,Realm 会通知您的处理程序。首先,使用asFlow()
从collection中创建 Kotlin 流程。接下来,使用collect()
方法处理该流上的事件。 类型为ListChange
、 SetChange
或MapChange
的Events记录对collection的所有更改。
RealmList
通知实现了ListChange接口,该接口描述了 RealmList集合中可能发生的更改。 这些状态由子类表示:
InitialList
UpdatedList
DeletedList
ListChangeSet是一个对列表中可能发生的变更进行建模的接口,其属性包括:
属性 | 类型 | 说明 |
insertions | IntArray | 此版本中添加的新集合的索引。 |
insertionRanges | Array<ListChangeSet.Range> | 此版本中添加的新collection中的索引范围。 |
changes | IntArray | 此版本中修改的新集合中对象的索引。 |
changeRanges | Array<ListChangeSet.Range> | 在此版本中修改的新集合中的索引范围。 |
deletions | IntArray | 先前版本的集合索引已从本版本中删除。 |
deletionRanges | Array<ListChangeSet.Range> | 已从本版本中删除的上一个版本的collection中的索引范围。 |
list | RealmResults<T 作为 RealmObject> | 正在监控结果collection的更改。当事件类型为 UpdatedList 时提供此选项。 |
RealmSet
通知实现了SetChange接口,该接口描述了 RealmSet集合中可能发生的更改。 这些状态由子类表示:
InitialSet
UpdatedSet
DeletedSet
SetChangeSet是一个接口,用于对设立中可能发生的更改进行建模,属性包括:
属性 | 类型 | 说明 |
deletions | Int | 此版本的集合中已删除的条目数。 |
insertions | Int | 已在此版本的集合中插入的条目数。 |
set | RealmSet<T> | 正在监控更改的设立。 当事件类型为 UpdatedSet 时提供此选项。 |
RealmMap
通知实现了MapChange接口,该接口描述了 RealmMap集合中可能发生的更改。 这些状态由子类表示:
InitialMap
UpdatedMap
DeletedMap
MapChangeSet是一个对地图中可能发生的变更进行建模的接口,其属性包括:
属性 | 类型 | 说明 |
deletions | 数组<K> | 此版本的映射中已删除的键。 |
insertions | 数组<K> | 已插入此版本的映射中的键。 |
changes | 数组<K> | 在此版本的集合中其值已更改的键。 |
set | RealmMap<K, V> | 正在监控地图的更改。 当事件类型为 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 } } } }
注册键路径变更侦听器
版本 1.13.0 中的新增内容。
注册通知处理程序时,可以传递可选的字符串属性名称列表,以指定要监视的一个或多个键路径。
当您指定键路径时,只有这些键路径的更改才会trigger通知块。任何其他更改都不会trigger通知块。
在以下示例中,我们为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() }
注意
同一对象上的多个通知令牌(针对单独的键路径进行过滤)不会进行排他性过滤。如果一项键路径变更符合一个通知令牌的条件,则该对象的所有通知令牌块都将执行。
观察嵌套键路径
您可以使用点表示法来观察嵌套的键路径。 默认情况下,SDK 仅报告嵌套深度不超过四层的对象的通知。 如果需要观察嵌套更深的对象的更改,请观察特定的键路径。
在以下示例中,我们将监视嵌套属性members.age
的更新。 请注意,即使我们正在监视嵌套属性的更改,SDK 也会报告顶级members
属性的更改:
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() }
使用通配符观察键路径
您可以使用通配符 ( *
) 来观察通配符级别所有键路径的更改。
在以下示例中,我们使用通配符监视对members
属性内一层深度的任何嵌套属性的更改。 根据此示例中使用的模型,此更改侦听器将报告对任何成员的age
、 species
或name
属性的更改。 请注意,即使我们正在监视嵌套属性的更改,SDK 也会报告顶级members
属性的更改:
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() }
取消订阅变更侦听器
当您不想再接收变更监听器所监控数据的更新通知时,请取消订阅变更监听器。 要取消订阅变更侦听器, 请取消封闭的协程。
// 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
变更通知限制
嵌套文档中深度超过四级的变更不会触发变更通知。
如果您的数据结构需要侦听第五层深度或更深层的更改,解决方法包括:
重构模式以减少嵌套。
添加“推送以刷新”一类的内容,使用户能够手动刷新数据。