托管同步订阅 - Kotlin SDK
在此页面上
使用订阅和权限来确定要与您的应用同步哪些数据。 本页详细介绍如何管理Flexible Sync的订阅。
有关详细信息,请参阅 App Services 文档中的 Flexible Sync 。
先决条件
在使用 Flexible Sync 订阅之前,必须将应用设置为使用 Device Sync 并在后端配置 Flexible Sync。
为此,请完成“将 Device Sync 添加到您的应用程序”中概述的步骤。
订阅概述
当您在后端配置“灵活同步”时,可指定客户端应用程序可使用订阅查询哪些字段。
每个订阅对应于对特定对象类型的可查询字段的查询。 有关更多信息,请参阅 Atlas App Services文档中的可查询 字段 。
对于每个查询订阅,Realm 都会查找与查询匹配的数据。与订阅匹配的数据(其中用户具有适当的权限)在客户端和后端应用程序之间同步。
您可以使用Realm Query Language 构建查询。
重要
Flexible Sync不支持 RQL 中提供的所有操作符。 有关详细信息,请参阅“ Flexible Sync RQL 限制”。
订阅对象类型
订阅集基于对象类型。如果您有多种类型的 Realm 对象,则可能有多个订阅。您还可以对同一对象类型拥有多个订阅。
但是,如果您在应用中使用了关联对象、不对称对象或地理空间数据,请参阅以下部分以了解更多信息:
链接对象
如果您的应用程序使用链接对象,则必须将对象本身及其链接对象都添加到订阅集中才能查看实际的链接对象。
当您的订阅结果包含一个对象,而该对象的属性链接到结果中未包含的对象时,该链接将显示为空。 无法区分该属性的值是否为 null,或者它链接到的对象是否存在但不在查询订阅的视图中。
不对称对象
如果应用程序使用数据导入单向同步不对称对象,则无法为这些对象创建订阅。如果应用程序包含同一域中的不对称对象和非不对称对象,则您可以为非不对称对象添加“Flexible Sync”订阅查询。
地理空间数据
在版本 1.13.0 中进行了更改: Atlas Device Sync 支持的地理空间数据
在 Kotlin SDK 版本 1.13.0 及更高版本中,您可以创建地理空间查询的订阅。 如果您尝试使用旧版本的 SDK 订阅地理空间查询,您将收到带有补偿写入的服务器错误。
有关详细信息,请参阅查询地理空间数据。
首次订阅
必须至少有一个订阅,才能读取或写入域。
在客户端应用程序中管理订阅
在客户端应用程序中,您可以添加、更新和删除对可查询字段的特定查询的订阅。这决定哪些数据会同步到客户端设备。
您可以:
添加具有可选订阅名称的订阅:
在 Kotlin SDK 版本 1.10.0 及更高版本中,您可以使用
.subscribe()
订阅RealmQuery
或RealmResults
。 这会自动将订阅添加到订阅集。使用
subscriptions
API 手动将订阅添加到订阅集。 如果出于性能优化或业务逻辑原因需要对订阅进行更多控制,请使用此 API。 有关更多信息,请参阅性能注意事项部分。
响应订阅状态
使用新查询更新订阅
删除对象类型的单个订阅或所有订阅
关于本页中的示例
本页上的示例使用任务列表应用的数据集。
两种 Realm 对象类型分别是Team
和Task
:
class Task : RealmObject { var _id: ObjectId = ObjectId() var taskName: String = "" var assignee: String? = null var completed: Boolean = false var progressMinutes: Int = 0 var dueDate: RealmInstant? = null } class Team : RealmObject { var _id: ObjectId = ObjectId() var teamName: String = "" var tasks: RealmList<Task>? = realmListOf() var members: RealmList<String> = realmListOf() }
本页上的示例还假设您有授权用户和 Flexible Sync SyncConfiguration():
// Login with authorized user and define a Flexible Sync SyncConfiguration val app = App.create(YOUR_APP_ID) val user = app.login(credentials) val flexSyncConfig = SyncConfiguration.Builder(user, setOf(Task::class, Team::class)) .initialSubscriptions { // Define the initial subscription set for the realm ... } .build() // Open the synced realm and manage subscriptions val realm = Realm.open(flexSyncConfig) Log.v("Successfully opened realm: ${realm.configuration}")
订阅查询
版本 1.10.0 中的新增内容。
为了简化订阅管理,Realm Kotlin SDK 版本1 。 10 。 0添加了实验性.subscribe() API 以订阅RealmQuery
或RealmResults
集。此 API 抽象了通过订阅集手动添加和删除订阅的细节。
您可以:
使用可选名称自动订阅查询
使用新查询更新命名订阅
如果出于性能优化或业务逻辑原因需要更好地控制订阅,您可以使用subscriptions
API手动管理订阅集。 有关更多信息,请参阅性能注意事项部分。
订阅查询
您可以对查询执行.subscribe()
操作,为与特定查询匹配的对象创建订阅:
// Subscribe to a specific query val realmResults = realm.query<Task>("progressMinutes >= $0", 60) .subscribe() // Subscribe to all objects of a specific type val realmQuery = realm.query<Team>() realmQuery.subscribe()
这会创建一个未命名的订阅并将其添加到MutableSubscriptionSet
中,而不是要求您手动将该订阅添加到订阅集。
使用名称订阅查询
如果您的应用程序支持多个订阅,或者您想要更新订阅,则可能需要在订阅查询时添加名称。稍后可以使用此名称更新订阅的查询或按名称删除查询。
要向订阅添加名称,请在调用.subscribe()
时传递一个字符串:
// Add a subscription named "team_developer_education" val results = realm.query<Team>("teamName == $0", "Developer Education") .subscribe("team_developer_education")
更新查询订阅
您可以通过将updateExisting
设置为true
,使用新查询更新命名查询订阅:
// Create a subscription named "bob_smith_teams" val results = realm.query<Team>("$0 IN members", "Bob Smith") .subscribe("bob_smith_teams") // Add another subscription with the same name with `updateExisting` set to true // to replace the existing subscription val updateResults = realm.query<Team>("$0 IN members AND teamName == $1", "Bob Smith", "QA") .subscribe("bob_smith_teams", updateExisting = true)
这会自动更新订阅,而无需您手动更新订阅集中的订阅。
等待查询订阅同步
当您订阅查询的结果集时,该结果集在同步之前不包含对象。 如果应用程序创建对象,则可能无需在用户使用同步数据之前进行下载。 但是,如果您的应用程序需要来自服务器的数据才能让用户使用,则您可以指定应用程序应等待数据同步。 这会阻止应用执行,直到数据从服务器同步。
val results = realm.query<Team>("$0 IN members", "Bob Smith") .subscribe("bob_smith_teams", updateExisting = false, WaitForSync.ALWAYS) // After waiting for sync, the results set contains all the objects // that match the query - in our case, 1 println("The number of teams that have Bob Smith as a member is ${results.size}")
此选项使用WaitForSync枚举,其值为:
FIRST_TIME
:(默认)在应用程序最初创建订阅时等待下载匹配对象。 否则,无需等待新下载即可返回。 当您最初添加订阅时,应用程序必须具有互联网连接才能下载数据。 您可以选择指定一个timeout
值。ALWAYS
:每次调用.subscribe()
方法时等待下载匹配对象。 应用程序必须有互联网连接才能下载数据。 您可以选择指定一个timeout
值。NEVER
:永远不要等待下载匹配对象。 该应用需要互联网连接才能让用户在首次启动应用时进行身份验证,但可以在后续启动时使用缓存的凭证离线打开。
手动管理订阅
在客户端应用程序中,使用subscriptions
API 托管一组订阅。使用此 API,您可以添加、更新或删除对可查询字段的特定查询。 这些查询决定哪些数据同步到客户端设备。
在 Kotlin SDK 版本1中。 10 。 0及更高版本,可以使用.subscribe() API自动添加订阅。此 API 直接从RealmQuery
或RealmResults
添加对MutableSubscriptionSet
的订阅。
添加订阅
您可以添加未命名订阅或已命名订阅。
提示
指定订阅名称
如果您的应用程序使用多个订阅,请始终指定订阅名称。 这使得您的订阅在应用程序的其他位置更容易查找、更新和删除。
要手动创建订阅,请将订阅添加到订阅更新区块中。 您将每个新订阅附加到客户端的 Realm 订阅中。
realm.subscriptions.update { add( realm.query<Task>("progressMinutes >= $0",60) ) }
您还可以在创建订阅时指定订阅名称:
// Add a subscription named "team_dev_ed" realm.subscriptions.update { realm -> add( realm.query<Team>("teamName == $0", "Developer Education"), name = "team_dev_ed" ) }
重要
对象链接
要查看链接对象,必须将对象及其链接对象都添加到订阅集中。
如果订阅结果包含一个对象,而该对象的属性链接到结果中未包含的对象,则该链接显示为空。 我们无法区分该属性的值是否为 null,或者所链接的对象是否存在,但由于缺少订阅而无法供客户端使用。
通过初始订阅引导 Realm
您必须至少有一个订阅,才能读取或写入 Realm。当您使用SyncConfiguration()打开 Realm 时,可以使用初始订阅集来引导 Realm。有关更多信息,请参阅打开同步 Realm 。
将initialSubscriptions
参数与要用于引导 Realm 的订阅查询一起传递:
// Bootstrap the realm with an initial query to subscribe to val flexSyncConfig = SyncConfiguration.Builder(user, setOf(Team::class, Task::class)) .initialSubscriptions { realm -> add( realm.query<Team>("$0 IN members", "Bob Smith"), "bob_smith_teams" ) } .build()
如果您的应用需要在每次启动时重新运行初始订阅,您可以传递一个附加参数: rerunOnOpen
。 这是一个布尔值,表示初始订阅是否应在每次应用程序启动时重新运行。 您可能需要执行此操作以重新运行动态时间范围或其他需要重新计算订阅的静态变量的查询。
在此示例中,我们只需要未完成的任务。 将rerunOnOpen
设置为true
后,每次启动应用时,查询都会根据所需的查询结果动态地重新计算要同步的相关对象:
// `rerunOnOpen` lets the app recalculate this query every time the app opens val rerunOnOpenConfig = SyncConfiguration.Builder(user, setOf(Team::class, Task::class)) .initialSubscriptions(rerunOnOpen = true) { realm -> add( realm.query<Team>("completed == $0", false) ) } .build()
等待订阅更改同步
在本地写入订阅集的更新只是更改订阅的其中一个组成部分。 本地订阅更改后,客户端会与服务器同步,以解决由于订阅更改而导致的任何数据更新问题。这可能意味着在同步 Realm 中添加或删除数据。
使用SyncConfiguration.waitForInitialRemoteData()构建器方法强制应用程序处于阻塞状态,直到客户端订阅数据同步到后端,然后再打开 Realm:
// Update the list of subscriptions realm.subscriptions.update { add( realm.query<Team>("$0 IN members", "Jane Doe"), "jane_doe_teams" ) } // Wait for subscription to fully synchronize changes realm.subscriptions.waitForSynchronization(Duration.parse("10s"))
还可以使用SubscriptionSet.waitForSynchronization()延迟执行,直到实例化同步连接后订阅同步完成。
订阅设置状态
使用 SubscriptionSet.state 属性读取订阅集的当前状态。
SUPERCEDED
(原文如此 — 请注意备用拼写)是另一个线程在订阅集的不同实例上写入订阅时可能发生的 SubscriptionSetState。 如果状态变为SUPERCEDED
,则必须先获取订阅集的新实例,然后才能写入。
注意
订阅状态 Complete(完成)
订阅集状态“完成”并不意味着“同步已完成”或“所有文档已同步”。“完成”意味着发生了以下两件事:
该订阅已成为当前正在与服务器同步的活动订阅集。
在将订阅发送到服务器时与订阅匹配的文档现在位于本地设备上。请注意,这并不一定包括当前与订阅匹配的所有文档。
对于所有与订阅匹配的文档是否已同步到设备,Realm SDK 不提供检查方法。
使用新查询更新订阅
您可以使用SubscriptionSet.update() 更新订阅。
在此示例中,我们使用MutableSubscriptionSet.add() 。 更新名为"bob_smith_teams"
的订阅的查询。 必须将updateExisting
参数设置为true
才能使用add()
更新订阅:
// Create a subscription named "bob_smith_teams" realm.subscriptions.update { add( realm.query<Team>("$0 IN members", "Bob Smith"), "bob_smith_teams" ) } // Set `updateExisting` to true to replace the existing // "bob_smith_teams" subscription realm.subscriptions.update { add( realm.query<Team>("$0 IN members AND $1 IN members", "Bob Smith", "Jane Doe"), "bob_smith_teams", updateExisting = true ) }
您无法更新在没有名称的情况下创建的订阅。 但是,您可以通过查询查找未命名的订阅,将其从订阅集中删除,然后使用更新的查询添加新订阅:
// Search for the subscription by query val subscription = realm.subscriptions.findByQuery( realm.query<Team>("teamName == $0", "Developer Education") ) // Remove the returned subscription and add the updated query if (subscription != null) { realm.subscriptions.update { remove(subscription) add( realm.query<Team>("teamName == $0", "DevEd"), "team_developer_education" ) } }
删除订阅
要删除订阅,您可以:
删除单个订阅查询
删除特定对象类型的所有订阅
删除所有订阅
删除所有未命名的订阅
当您删除订阅查询时,Realm 会异步删除与客户端设备中的查询匹配的同步数据。
删除单个订阅
您可以使用MutableSubscriptionSet.remove()删除特定的订阅查询。 您可以按名称查找订阅,然后将返回的订阅传递给remove()
,或将订阅名称直接传递给remove()
:
realm.subscriptions.update { add( realm.query<Team>("$0 IN members", "Bob Smith"), "bob_smith_teams" ) } // Wait for synchronization to complete before updating subscriptions realm.subscriptions.waitForSynchronization(Duration.parse("10s")) // Remove subscription by name realm.subscriptions.update { remove("bob_smith_teams") }
删除对象类型的所有订阅
如果要删除对特定对象类型的所有订阅,请将一个类传递给MutableSubscriptionSet.removeAll() 。方法:
realm.subscriptions.update { add( realm.query<Team>("$0 IN members", "Bob Smith"), "bob_smith_teams") } // Wait for synchronization to complete before updating subscriptions realm.subscriptions.waitForSynchronization(Duration.parse("10s")) // Remove all subscriptions to type Team realm.subscriptions.update { removeAll(Team::class) }
删除所有订阅
要从订阅集中删除所有订阅,请使用MutableSubscriptionSet.removeAll() 。不带参数:
警告
如果删除所有订阅且未添加新订阅,则会出现错误。使用 Flexible Sync 配置打开的 Realm 至少需要一个订阅才能与服务器同步。
// Remove all subscriptions realm.subscriptions.update { removeAll() }
删除所有未命名的订阅
版本 1.10.0 中的新增内容。
您可能想要删除临时或动态生成的未命名订阅,但保留已命名订阅。
您可以在调用removeAll
方法时将anonymousOnly
设置为true
,以从订阅集中删除所有未命名(匿名)订阅:
// Remove all unnamed (anonymous) subscriptions realm.subscriptions.update { removeAll(anonymousOnly = true) }
“灵活同步”RQL 的要求和限制
已索引可查询字段订阅的要求
向应用添加索引可查询字段可以提高对强分区数据进行简单查询的性能。 例如,如果查询将数据强映射到设备、商店或用户的应用(例如user_id == $0, “641374b03725038381d2e1fb”
,则非常适合使用索引可查询字段。 但是,在查询订阅中使用索引化可查询字段有特定的要求:
每个订阅查询中都必须使用索引化可查询字段。查询中不能缺少该字段。
在订阅查询中,索引化可查询字段必须使用
==
或IN
进行至少一次针对常量的比较。例如,user_id == $0, "641374b03725038381d2e1fb"
或store_id IN $0, {1,2,3}
。
可以选择包含 AND
比较,前提是使用 ==
或 IN
将索引化可查询字段直接与常量进行至少一次比较。例如,store_id IN {1,2,3} AND region=="Northeast"
或 store_id == 1 AND (active_promotions < 5 OR num_employees < 10)
。
对索引化可查询字段的无效灵活同步查询包括以下情况的查询:
索引化可查询字段未将
AND
与查询的其余部分结合使用。例如,store_id IN {1,2,3} OR region=="Northeast"
是无效的,因为它使用了OR
而不是AND
。同样,store_id == 1 AND active_promotions < 5 OR num_employees < 10
也是无效的,因为AND
仅适用于其旁边的词,而不适用于整个查询。索引化可查询字段未在相等运算符中使用。例如,
store_id > 2 AND region=="Northeast"
是无效的,因为它仅将>
运算符与索引化可查询字段结合使用,而没有相等比较。查询中完全没有索引化可查询字段。例如,
region=="Northeast
和truepredicate
都是无效的,因为它们不含索引化可查询字段。
灵活同步中不支持的查询运算符
使用 RQL 操作符时,Flexible Sync有一些限制。当您编写确定要同步哪些数据的查询订阅时,服务器不支持这些查询操作符。但是,您仍然可以使用全部的 RQL 功能来查询客户端应用中的同步数据集。
运算符类型 | 不支持的运算符 |
---|---|
聚合操作符 | @avg , @count , @max , @min , @sum |
查询后缀 | DISTINCT , SORT , LIMIT |
不区分大小写的查询 ([c]
) 无法有效地使用索引。因此,不建议使用不区分大小写的查询,因为它们可能会导致性能问题。
灵活同步仅支持数组字段的 @count
。
列出查询
灵活同步支持使用 IN
运算符来查询列表。
可以查询常量列表,查看其中是否包含可查询字段的值:
// Query a constant list for a queryable field value "priority IN { 1, 2, 3 }"
如果某个可查询字段具有数组值,则可以通过查询确定其中是否包含常量值:
// Query an array-valued queryable field for a constant value "'comedy' IN genres"
警告
您无法在灵活同步查询中相互比较两个列表。请注意,这是灵活同步查询之外的有效 Realm 查询语言语法。
// Invalid Flexible Sync query. Do not do this! "{'comedy', 'horror', 'suspense'} IN genres" // Another invalid Flexible Sync query. Do not do this! "ANY {'comedy', 'horror', 'suspense'} != ANY genres"
嵌入式对象或关联对象
灵活同步不支持查询嵌入式对象或链接中的属性。例如,obj1.field == "foo"
。
查询大小限制
订阅集中任何给定查询订阅的大小限制为256 kB 。超过此限制会导致LimitsExceeded 错误。
性能考虑因素
API 效率
通过订阅集 API 手动管理订阅时,使用“订阅查询”部分中描述的.subscribe()
API 管理多个订阅比执行批量更新效率低。 为了在进行多项订阅更改时获得更好的性能,请使用“手动管理订阅”部分中描述的subscriptions.update
API。
更新群组以提高性能
订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。