Docs 菜单
Docs 主页
/ /
Atlas Device SDKs
/ /

托管同步订阅 - Flutter SDK

在此页面上

  • 先决条件
  • 将订阅与后端应用保持一致
  • 订阅查询
  • 订阅查询
  • 等待查询订阅同步
  • 取消订阅查询
  • 手动管理订阅
  • 获取订阅
  • 向订阅集添加查询
  • 使用新查询更新订阅
  • 删除订阅
  • 等待订阅更改同步
  • 订阅状态
  • “灵活同步”RQL 的要求和限制
  • 已索引可查询字段订阅的要求
  • 灵活同步中不支持的查询运算符
  • 列出查询
  • 嵌入式对象或关联对象
  • 查询大小限制
  • 性能考虑因素
  • API 效率
  • 更新群组以提高性能

Atlas Device Sync 和 Flexible Sync 使用订阅和权限来确定要在 Atlas 和您的应用之间同步哪些数据。

您可以添加、更新和删除查询订阅,以确定哪些数据同步到客户端设备。

注意

Flexible Sync 先决条件

在应用中启用 Flexible Sync 需要一个运行 MongoDB 5.0或更高版本的非分片Atlas 集群

要在 Flutter 应用程序中使用 Flexible Sync,请执行以下操作:

  1. 在 Atlas App Services 后端配置 Flexible Sync

  2. 初始化应用客户端

  3. 在客户端中对用户进行身份验证

  4. 在客户端中打开同步域

客户端订阅查询必须与后端 App Services 应用程序中的 Device Sync 配置保持一致。

订阅查询可以:

v1.6.0 版本新增

Flutter v1.6.0 添加了实验性 API,用于订阅和取消订阅查询结果。 这些 API 抽象了手动添加和删除订阅的细节。

对于所有订阅,您需要经过身份验证的用户同步域。

如果出于性能优化或业务逻辑原因需要更好地控制订阅,您可以使用 subscriptions API手动管理订阅集。 有关更多信息,请参阅本页的“性能注意事项”部分。

您可以使用RealmResults subscribe() 订阅 查询的 方法。调用时,SDK 会创建新订阅并将其添加到MutableSubscriptionSet中,类似于手动创建订阅。

您可以选择为查询传递唯一的订阅名称。 如果添加的订阅与现有订阅同名,SDK 将覆盖现有订阅。

如果不传递订阅名称,则名称设置为null ,并且订阅标识符基于查询字符串。 这意味着,每当查询字符串发生变化时, subscribe()都会创建一个新的订阅。

要订阅查询,请将以下参数传递给subscribe()

  • RealmResults query:必需。 您可以使用Realm Query Language创建的RealmResults对象。

  • String name:可选。 可供引用的订阅名称。

  • bool update:可选。 如果为 true,则添加具有现有名称的订阅会将现有查询替换为新查询。 如果为 false,则 SDK 会针对重复订阅引发异常。 仅与命名订阅一起使用。

在以下示例中,我们订阅了两个新的命名查询。

final boatQuery = realm.all<Boat>();
final bigPlaneQuery = realm.query<Plane>("numSeats > 100");
final boatSubscription = await boatQuery.subscribe(name: "boats");
final planeSubscription =
await bigPlaneQuery.subscribe(name: "big-planes");

提示

指定订阅名称

我们建议您始终指定订阅名称,尤其是当您的应用程序使用多个订阅时。 这样可以更轻松地查找和管理您的订阅。

当您订阅查询结果时,在下载同步数据之前,结果不包含对象。 当您确实需要等待同步对象完成下载时,请配置 waitForSyncMode 选项。

此示例使用 firstTime 选项,这是默认行为。具有 firstTime 行为的订阅仅在首次创建订阅时等待同步完成。

final bigPlaneQuery = realm.query<Plane>("numSeats > 100");
final planeSubscription = await bigPlaneQuery.subscribe(
name: "firstTimeSync",
waitForSyncMode: WaitForSyncMode.firstTime,
);

其他受支持的 waitForSyncMode 选项包括:

  • always:每次应用程序启动时等待下载匹配对象。该应用程序在每次启动时都必须有互联网连接。

  • never:永远不要等待下载匹配对象。 该应用需要互联网连接才能让用户在首次启动应用时进行身份验证,但可以使用缓存的凭据在后续启动时离线打开。

您可以选择指定一个 cancelToken 限制同步下载运行的时间:

final bigPlaneQuery = realm.query<Plane>("numSeats > 200");
final planeSubscription = await bigPlaneQuery.subscribe(
name: "alwaysWaitSync",
waitForSyncMode: WaitForSyncMode.always,
cancellationToken: TimeoutCancellationToken(Duration(seconds: 5)),
);

除非您取消订阅,否则订阅将在用户会话中持续存在。 您可以使用 unsubscribe() 取消订阅查询结果。

这将从活动订阅列表中删除订阅,类似于手动删除订阅。 请注意,如果存在另一个包含重叠对象的订阅,则在调用unsubscribe()后,结果列表可能仍包含对象。

当您对查询调用unsubscribe()时,SDK 会删除所有具有与您调用unsubscribe()的查询完全匹配的查询的订阅。 此方法会在与已删除订阅匹配的对象从 Realm 中删除之前返回。 同步会根据新的订阅集在背景继续进行。

planeQuery.unsubscribe();
trainQuery.unsubscribe();

在后端配置 Flexible Sync 时,您可以指定客户端应用程序可以查询哪些字段。 在客户端应用程序中,使用 Realm.subscriptions属性来管理对可查询字段的特定查询的一组订阅。

您可以对订阅执行以下操作:

  • 获取所有订阅的列表

  • 添加订阅

  • 检查订阅状态

  • 使用新查询更新订阅

  • 删除订阅

当数据与订阅匹配,且通过身份验证的用户拥有相应权限时,Device Sync 会将后端数据与客户端应用程序同步。

即使您不再在代码中包含订阅,订阅集也会跨会话持续存在。 订阅信息存储在同步 Realm 的数据库文件中。 您必须显式删除订阅,它才会停止同步匹配数据的尝试。

您可以为订阅指定字符串名称。 如果没有为订阅命名,则名称将设置为null

创建订阅时,Realm 会查找与 Realm 对象类型的查询匹配的数据。在Flexible Sync订阅中,您可以对几种不同的Realm 对象类型进行订阅,也可以对同一Realm 对象类型进行多次查询。

使用Flexible Sync时,您可以访问 SubscriptionSet ,通过Realm.subscriptions 属性访问的订阅集合。

您可以使用此订阅集向此订阅列表添加查询并更新现有订阅,如以下示例所示。

final subscriptions = realm.subscriptions;

您必须对更新区块内的订阅集执行所有变更。 要创建更新区块,请调用 SubscriptionSet.update()。

更新区块回调函数包括 MutableSubscriptionSet() 对象作为参数。您可以修改SubscriptionSet上的方法,向订阅添加查询。

重要

Flexible Sync不支持 RQL 中提供的所有操作符。 有关详细信息,请参阅“ Flexible Sync RQL 限制”。

MutableSubscriptionSet.add()方法接受三个参数:

  • RealmResults query:必需。 您可以使用Realm Query Language 查询创建的RealmResults对象。

  • String name:可选。 可供引用的订阅名称。

  • bool update:可选。 如果为 true,则添加具有现有名称的订阅会将现有查询替换为新查询。 仅与命名订阅一起使用。

注意

重复订阅

如果您使用相同的查询添加重复的 未命名 订阅,Realm 会自动将其删除;添加相同的 命名 订阅是 无需操作的 。因此,在这两种情况下,重复订阅都会被忽略。

您可以添加单个查询,或在SubscriptionSet.update区块中批处理多个查询。在服务器上执行查询更新是一项成本高昂的操作。 我们强烈建议您在设计应用程序时尽量减少订阅更新。 为此,您可以在用户首次启动应用时在单个更新区块中创建所有订阅,并批处理对订阅集进行任何后续更改。

在下面的示例中,我们订阅了两个查询。

final planeQuery = realm.all<Plane>();
final longTrainQuery = realm.query<Train>("numCars >= 5");
realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.add(planeQuery, name: "planes");
mutableSubscriptions.add(longTrainQuery,
name: 'long-trains', update: true);
});
await realm.subscriptions.waitForSynchronization();

您可以使用新查询更新命名订阅。 要更新订阅的查询,请使用SubscriptionSet.update()打开更新区块。 在更新块的回调函数中,将以下参数传递给MutableSubscriptionSet.add()

  • 新查询

  • 要更新的订阅的名称

  • update: true

您无法更新未命名的订阅。 或者,您可以删除未命名的订阅,然后使用所需的查询创建新的订阅

在以下示例中,长列车被重新定义为任何车厢超过 10 节的列车。

final longerTrainQuery = realm.query<Train>("numCars > 10");
realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.add(longerTrainQuery,
name: 'long-trains', update: true);
});

要从订阅集中删除订阅,您可以:

  • 使用给定查询删除单个订阅

  • 删除具有给定名称的单个订阅

  • 删除具有订阅引用的单个订阅

  • 删除 Realm 对象类型的所有订阅

  • 删除所有订阅

删除订阅查询时,服务器也会删除客户端设备上的同步数据。

在更新区块内,您可以通过查询删除特定订阅。 使用SubscriptionSet.update()打开更新区块。 将Subscription 传递给 MutableSubscriptionSet.removeByQuery()。

在以下示例中,删除了所有Plane对象的订阅。

realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.removeByQuery(realm.all<Plane>());
});

在更新区块中,您可以按名称删除特定订阅。 将名称传递给 MutableSubscriptionSet.removeByName()

realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.removeByName('long-trains');
});

如果您引用了订阅的订阅,则可以删除 订阅 对象。在订阅更新区块中,将Subscription 引用传递给 MutableSubscriptionSet.remove()。

final sub = realm.subscriptions[0];
realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.remove(sub);
});

您可以删除给定 Realm 对象类型的所有订阅。 在订阅更新区块中,调用 MutableSubscriptionSet.removeByType()。

realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.removeByType<Train>();
});

在订阅更新区块中,您可以使用 MutableSubscriptionSet.clear() 从订阅集中删除所有未命名的订阅。

realm.subscriptions.update((MutableSubscriptionSet mutableSubscriptions) {
mutableSubscriptions.clear();
});

在更新区块内更改订阅集只是更改订阅的一部分。 本地订阅更改后,域会与服务器同步,以解决由于订阅更改而导致的任何数据更新问题。这包括在同步 Realm 中添加或删除数据。

使用 Realm.subscriptions.waitForSynchronization()等待服务器确认这组订阅。 如果服务器拒绝更改,则会引发异常。

如果出现以下情况,则可能会出现异常:

  • 您订阅的查询不受支持。 订阅不支持的查询将暂停同步。 要恢复同步,请删除不支持的查询。

  • 您正在执行无效的操作,例如添加与订阅不匹配的对象。 此Atlas Triggers客户端重置:从 Realm 中删除数据,并创建数据的新副本,且集合中不含任何订阅。

await realm.subscriptions.waitForSynchronization();

使用 Realm.subscriptions.state 属性来读取订阅集的当前状态。

superseded状态是 SubscriptionSetState 当另一个线程在订阅集的不同实例上更新订阅时,可能会发生这种情况。如果状态变为superseded ,则必须先获取订阅集的新实例,然后才能进行更新。

注意

订阅状态 Complete(完成)

订阅集状态“完成”并不意味着“同步已完成”或“所有文档已同步”。“完成”意味着发生了以下两件事:

  • 该订阅已成为当前正在与服务器同步的活动订阅集。

  • 在将订阅发送到服务器时与订阅匹配的文档现在位于本地设备上。请注意,这并不一定包括当前与订阅匹配的所有文档。

对于所有与订阅匹配的文档是否已同步到设备,Realm SDK 不提供检查方法。

向应用添加索引可查询字段可以提高对强分区数据进行简单查询的性能。 例如,如果查询将数据强映射到设备、商店或用户的应用(例如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=="Northeasttruepredicate 都是无效的,因为它们不含索引化可查询字段。

使用 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 手动管理订阅时,使用“订阅查询”部分中描述的.subscribe() API 管理多个订阅比执行批量更新效率低。 为了在进行多项订阅更改时获得更好的性能,请使用“手动管理订阅”部分中描述的subscriptions.update API。

订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。

后退

打开同步 Realm