管理同步订阅 - React Native SDK
在此页面上
灵活同步使用订阅和权限来确定要与您的应用同步哪些数据。您必须至少拥有一项订阅,才能读取或写入启用了灵活同步的 Realm。 @realm/react
库简化了同步订阅的权限和查询。
您可以添加、更新和删除查询订阅,以控制同步到客户端设备的数据。在 Realm.js v12.0.0 及更高版本中,您可以订阅查询,而不是手动管理订阅,或作为手动管理订阅的补充。
您无法为数据导入和非对称对象创建订阅,因为它们仅将数据发送到App Services后端。
先决条件
您需要满足以下要求才能将 Atlas Device Sync 与 React Native SDK 结合使用:
运行MongoDB 5.0或更高版本的非分片Atlas 集群。
Realm.js v10.12.0 或更高版本。
在将 Flexible Sync 订阅添加到 React Native 客户端之前,您必须:
将订阅与后端应用保持一致
客户端订阅查询必须与后端 App Services 应用程序中的 Device Sync 配置保持一致。
订阅查询返回某一类型的所有对象。您可以使用包含一个或多个可查询字段的 Realm 查询语言查询筛选结果。
要了解有关配置可查询字段的更多信息,请参阅 App Services 文档中的可查询字段。
要了解在“Flexible Sync”中使用 Realm Query Language 的限制的详情,请参阅“Flexible Sync”RQL 限制部分。
订阅查询
realm@12.0.0
版本中的新增功能。
realm@12.0.0
添加用于订阅和取消订阅查询结果的实验性 API。这些 API 采用抽象方式表示手动添加和删除订阅的细节。
对于所有订阅,您都需要经过身份验证的用户和 Flexible Sync Realm。
realm@12.3.0
版变更内容:Atlas Device Sync 支持的地理空间数据
在 Realm.js v12.3.0 及更高版本中,您可以创建地理空间查询的订阅。如果您尝试使用旧版本的 SDK 订阅地理空间查询,您将收到带有补偿写入的服务器错误。
有关详细信息,请参阅地理空间 - React Native SDK。
订阅查询
我们建议您为订阅命名。这样可以更轻松地查找和管理您的订阅。订阅名称必须是唯一名称。尝试添加与现有订阅名称相同的订阅会覆盖现有订阅。
订阅查询:
查询要读写的对象。
对查询结果调用
subscribe()
,为与查询匹配的对象创建同步订阅。将包含
name
属性的SubscriptionOptions
对象传递给subscribe()
。
import React, {useEffect, useState} from 'react'; import {useRealm, useQuery} from '@realm/react'; import {View, Text, FlatList} from 'react-native'; import {Bird} from '../../models'; import {Subscription} from 'realm/dist/bundle'; export const BasicSubscription = () => { const realm = useRealm(); // Get all local birds that have not been seen yet. const seenBirds = useQuery(Bird, collection => collection.filtered('haveSeen == true'), ); const [seenBirdsSubscription, setSeenBirdsSubscription] = useState<Subscription | null>(); useEffect(() => { // Create an async function so that we can `await` the // promise from `.subscribe()`. const createSubscription = async () => { await seenBirds.subscribe({ name: 'Birds I have seen', }); }; createSubscription().catch(console.error); // Get the subscription... const subscription = realm.subscriptions.findByName('Birds I have seen'); // ... and set it to a stateful variable or manage it in `useEffect`. setSeenBirdsSubscription(subscription); }, []); return ( // Work with the subscribed results list or modify the subscription... <></> ); };
大多数情况下,您应该给订阅命名。否则,名称将设置为 null
。
如果您在未命名的查询订阅上使用 filtered()
,则订阅标识符基于 filtered
查询。这意味着,每当查询字符串变更时,subscribe()
就会创建新的订阅。
API 参考
等待查询订阅同步
如果您订阅查询结果,则在下载同步数据之前,结果不包含对象。如果您确实需要等待同步对象完成下载,请配置 waitForSync
选项。
此示例使用 FirstTime
选项,这是默认行为。具有 FirstTime
行为的订阅仅在首次创建订阅时等待同步完成。
import React, {useEffect, useState} from 'react'; import {BSON, WaitForSync} from 'realm'; import {useRealm, useQuery} from '@realm/react'; import {View, Text, Button, TextInput, FlatList} from 'react-native'; import {Bird} from '../../models'; import {Subscription} from 'realm/dist/bundle'; export const WaitFirstTime = () => { const realm = useRealm(); const [birdName, setBirdName] = useState('Change me!'); // Get local birds that have been marked as "haveSeen". const seenBirds = useQuery(Bird, collection => collection.filtered('haveSeen == true'), ); const [seenBirdsSubscription, setSeenBirdsSubscription] = useState<Subscription | null>(); useEffect(() => { const createSubscription = async () => { // Only wait for sync to finish on the initial sync. await seenBirds.subscribe({ behavior: WaitForSync.FirstTime, name: 'First time sync only', }); }; createSubscription().catch(console.error); // Get the subscription... const subscription = realm.subscriptions.findByName('First time sync only'); // ... and set it to a stateful variable or manage it in `useEffect`. setSeenBirdsSubscription(subscription); }, []); return ( // Work with the subscribed results list or modify the subscription... <></> ); };
其他受支持的 WaitForSync
选项包括:
Always
:每次应用程序启动时等待下载匹配对象。该应用程序在每次启动时都必须有互联网连接。Never
:永远不要等待下载匹配对象。该应用需要互联网连接才能让用户在首次启动应用时进行身份验证,但可以在后续启动时使用缓存的凭据离线打开。
您可以选择指定 timeout
值来限制同步下载运行的时间:
export const AlwaysWait = () => { const realm = useRealm(); // Get all local birds that have not been seen yet. const unSeenBirds = useQuery(Bird, collection => collection.filtered('haveSeen == false'), ); const [unSeenBirdsSubscription, setUnseenBirdsSubscription] = useState<Subscription | null>(); useEffect(() => { const createSubscription = async () => { // Add subscription with timeout. // If timeout expires before sync is completed, currently-downloaded // objects are returned and sync download continues in the background. await unSeenBirds.subscribe({ behavior: WaitForSync.Always, name: 'Always wait', timeout: 500, }); }; createSubscription().catch(console.error); // Get the subscription... const subscription = realm.subscriptions.findByName('Always wait'); // ... and set it to a stateful variable or manage it in `useEffect`. setUnseenBirdsSubscription(subscription); }, []); return ( // Work with the subscribed results list or modify the subscription... <></> ); };
API 参考
取消订阅查询
除非取消订阅,否则订阅会在用户会话中持续存在。您可以使用 unsubscribe()
取消订阅查询结果。
这将从活动订阅列表中删除订阅,类似于手动删除订阅。
如果存在另一个包含重叠对象的订阅,则在调用 unsubscribe()
后,结果列表可能仍包含对象。
当您调用 unsubscribe()
时,相关订阅将被删除。按名称删除订阅。如果它们没有名称,则 unsubscribe()
会删除与您调用 unsubscribe()
的查询完全匹配的查询。
unsubscribe()
方法会在与已删除订阅匹配的对象从 Realm 中删除之前返回。同步会根据新的订阅集在后台继续进行。
import React, {useEffect, useState} from 'react'; import {useRealm, useQuery} from '@realm/react'; import {View, Text, Button} from 'react-native'; import {Bird} from '../../models'; import {Subscription} from 'realm/dist/bundle'; export const Unsubscribe = () => { const realm = useRealm(); const birds = useQuery(Bird); const unsubscribeFromQuery = () => { birds.unsubscribe(); }; return ( <View> <Button title="Unsubscribe" onPress={() => { unsubscribeFromQuery(); }} /> </View> ); };
API 参考
手动管理订阅
您可以使用 Subscriptions API 手动管理对可查询字段的特定查询的订阅集。
您可以使用 Realm.subscriptions API 来管理对可查询字段的特定查询的订阅集。
如果您使用 @realm/react
,则可以在正确配置的 Realm 提供程序内管理 Realm 订阅。The useRealm() 钩子允许您访问当前打开的Realm。
您可以对订阅执行以下操作:
当数据与订阅匹配,且通过身份验证的用户拥有相应权限时,Device Sync 会将后端数据与客户端应用程序同步。
创建订阅时,Realm 会查找与特定对象类型的查询匹配的数据。您可以对几种不同的对象类型进行订阅。您还可以对同一对象类型进行多次查询。
重要
对象链接
要查看链接对象,必须将对象及其链接对象都添加到订阅集中。
如果订阅结果包含一个对象,该对象的属性链接到结果中未包含的对象,则该链接显示为空。我们无法区分该属性的值是否为合法空值,或者所链接的对象是否存在但不在查询订阅的视图中。
访问所有订阅
在为“灵活同步”配置的 RealmProvider
内,您可以访问 SubscriptionSet
。订阅集是应用程序所有订阅的集合。
import React, {useEffect, useState} from 'react'; import {Text, FlatList} from 'react-native'; import {useRealm, useQuery} from '@realm/react'; import {Bird} from '../Models/Bird'; function SubscriptionManager() { const realm = useRealm(); // Pass object model to useQuery and filter results. // This does not create a subscription. const seenBirds = useQuery(Bird, birds => { return birds.filtered('haveSeen == true'); }); const [subscriptions, setSubcriptions] = useState< App.Sync.SubscriptionSet | undefined >(); useEffect(() => { const createSubscription = async () => { // Create subscription for filtered results. await realm.subscriptions.update(mutableSubs => { mutableSubs.add(seenBirds, {name: 'seen birds'}); }); }; createSubscription().catch(console.error); // Set to state variable. setSubcriptions(realm.subscriptions); }, []); return ( <FlatList data={subscriptions} keyExtractor={subscription => subscription.id.toString()} renderItem={({item}) => <Text>{item.name}</Text>} /> ); }
API 参考
添加订阅
在以下示例中,completed
和 progressMinutes
已被设置为 App Services App 中的可查询字段。在客户端代码中,我们创建经筛选的查询,然后订阅其结果:
已完成的任务
耗时超过 120 的已完成任务
progressMinutes
请注意,useQuery()
需要活动订阅才能返回结果。如果尚未添加任何订阅,useQuery()
将返回空结果,这对于 MutableSubscriptionSet.add()
来说不是有效的 query
。
import React, {useEffect} from 'react'; import {Text, FlatList} from 'react-native'; import {useRealm, useQuery} from '@realm/react'; function SubscriptionManager() { const realm = useRealm(); const seenBirds = useQuery(Bird, birds => { return birds.filtered('haveSeen == true'); }); useEffect(() => { realm.subscriptions.update( (mutableSubs: Realm.App.Sync.MutableSubscriptionSet) => { // Create subscription for filtered collection. mutableSubs.add(seenBirds, {name: 'seenBirds'}); }, ); }); return ( <FlatList data={seenBirds} keyExtractor={item => item._id.toString()} renderItem={({item}) => <Text>{item._id.toString()}</Text>} /> ); }
使用初始订阅配置 Realm
必须至少有一个订阅,才能读取或写入 Flexible Sync Realm。初始订阅允许您在配置同步 Realm 时定义订阅。
要打开具有初始订阅的同步 Realm,请将 initialSubscriptions
属性添加到 RealmProvider
的同步配置中。
设置初始订阅时,您不能使用 @realm/react
库钩子 useQuery
和 useObject
。相反,请使用 Realm.js 读写操作。
import React from 'react'; import {AppProvider, UserProvider} from '@realm/react'; // get realm context from createRealmContext() import {RealmContext} from '../RealmConfig'; import {Text, FlatList} from 'react-native'; const {RealmProvider, useQuery} = RealmContext; function AppWrapper() { return ( <AppProvider id={APP_ID}> <UserProvider fallback={LogIn}> <RealmProvider sync={{ flexible: true, initialSubscriptions: { update(subs, realm) { subs.add(realm.objects('Turtle')); }, }, onError: console.log, }}> <SubscriptionManager /> </RealmProvider> </UserProvider> </AppProvider> ); } function SubscriptionManager() { // Pass object model to useQuery to get all objects of type `Turtle`. // These results automatically update with changes from other devices // because we created a subscription with `initialSubscriptions`. const allTurtles = useQuery('Turtle'); return ( <FlatList data={allTurtles} keyExtractor={turtle => turtle._id.toString()} renderItem={({item}) => <Text>{item._id}</Text>} /> ); }
默认情况下,仅在首次打开 Realm 时才会创建初始订阅。如果应用程序需要在每次启动时重新运行初始订阅,则您可以将 rerunOnOpen
设置为 true
。您可能需要执行此操作,重新运行动态时间范围或需要为订阅重新计算静态变量的其他查询。
API 参考
检查订阅状态
您可以检查订阅状态,查看服务器是否已确认订阅,设备是否已将数据下载到本地。
可以使用订阅状态执行以下操作:
Trigger error handling
显示事务是挂起还是已完成
了解订阅集被取代的时间,并且您应获取订阅集的新实例,以写入订阅变更
import React, {useEffect} from 'react'; import {Text, View} from 'react-native'; import {useRealm, useQuery} from '@realm/react'; import {Bird} from '../Models/Bird'; function SubscriptionManager() { const realm = useRealm(); const seenBirds = useQuery(Bird, birds => { return birds.filtered('haveSeen == true'); }); useEffect(() => { realm.subscriptions.update( (mutableSubs: Realm.App.Sync.MutableSubscriptionSet) => { // Create subscription for filtered collection. mutableSubs.add(seenBirds, {name: 'seenBirds'}); }, ); }); // Returns state of all subscriptions, not individual subscriptions. // In this case, it's just the subscription for `Bird` objects where // `haveSeen` is true. const allSubscriptionState = realm.subscriptions.state; return ( <View> <Text > Status of all subscriptions: {allSubscriptionState} </Text> </View> ); }
realm@12.0.0
版本中的新增功能。
Realm.js v 12.0.0 添加了可用于获取订阅状态的订阅枚举。
订阅状态 Complete(完成)
订阅集状态“完成”并不意味着“同步已完成”或“所有文档已同步”。“完成”意味着发生了以下两件事:
该订阅已成为当前正在与服务器同步的活动订阅集。
在将订阅发送到服务器时与订阅匹配的文档现在位于本地设备上。请注意,这并不一定包括当前与订阅匹配的所有文档。
对于所有与订阅匹配的文档是否已同步到设备,Realm SDK 不提供检查方法。
使用新查询更新订阅
您可以使用新查询更新已命名的订阅。要更新订阅的查询,请将新查询以及带有要更新的订阅名称的订阅选项传递给 MutableSubscriptionSet.add()
方法。与添加新订阅一样,您必须通过调用 subscriptions.update()
在事务中更新订阅。
以下示例将长时间运行任务重新定义为耗时超过 180 分钟的任务。
realm.subscriptions.update((mutableSubs) => { mutableSubs.add( tasks.filtered('status == "completed" && progressMinutes > 180'), { name: "longRunningTasksSubscription", } ); });
注意
尝试更新已将 SubscriptionOptions.throwOnUpdate
字段设置为 true 的订阅,会出现异常。
API 参考
删除订阅
即使您不再在代码中包含订阅,订阅集也会跨会话持续存在。订阅信息存储在已同步 Realm 的数据库文件中。您必须显示删除订阅,它才会停止同步匹配数据的尝试。
您可以通过以下方式删除订阅:
使用特定查询删除单个订阅
删除具有特定名称的单个订阅
删除对特定对象模型的所有订阅
删除所有未命名的订阅
删除所有订阅
删除订阅查询时,服务器也会删除客户端设备上的同步数据。
本部分的示例假设您正在使用 @realm/react
和正确配置的 RealmProvider。
import {useEffect} from 'react'; // get realm context from createRealmContext() import {RealmContext} from '../RealmConfig'; const {useRealm} = RealmContext; function SubscriptionManager() { const realm = useRealm(); useEffect(() => { realm.subscriptions.update(mutableSubs => { // Remove subscription for object type `Turtle`, // which we added in `initialSubscriptions`. mutableSubs.removeByObjectType('Turtle'); }); }); return ( // ... ); }
通过查询删除订阅
您可以在订阅集上执行事务,通过查询删除特定订阅。在写事务中将查询传递给 MutableSubscriptionSet.remove()。
realm.subscriptions.update((mutableSubs) => { // remove a subscription with a specific query mutableSubs.remove(tasks.filtered('owner == "Ben"')); });
按名称删除订阅
要按名称删除特定订阅,请在订阅设立上执行ACID 事务。 在ACID 事务中,将名称传递给MutableSubscriptionSet.removeByName()。
realm.subscriptions.update((mutableSubs) => { // remove a subscription with a specific name mutableSubs.removeByName("longRunningTasksSubscription"); });
通过引用删除订阅
如果您有对订阅的引用,则可以删除该订阅。 为此,请在订阅设立上执行ACID 事务。 在ACID 事务中,将引用变量传递给MutableSubscriptionSet.removeSubscription()。
let subscriptionReference; realm.subscriptions.update((mutableSubs) => { subscriptionReference = mutableSubs.add(realm.objects("Task")); }); // later.. realm.subscriptions.removeSubscription(subscriptionReference);
删除对象类型上的所有订阅
要删除特定对象类型的所有订阅,请在订阅集上执行事务。 在事务中,将对象类型作为string传递给MutableSubscriptionSet.removeByObjectType()。
realm.subscriptions.update((mutableSubs) => { mutableSubs.removeByObjectType("Team"); });
删除所有未命名的订阅
realm@v12.0.0
版本中的新增功能。
您可能想要删除临时或动态生成的未命名订阅,但保留已命名订阅。
您可以通过在 mutableSubs
上调用 .removeUnnamed()
,从订阅集中删除所有未命名的订阅。.removeUnnamed()
将返回已删除的未命名订阅的数量。
// Remove unnamed subscriptions. let numberRemovedSubscriptions = 0; await realm.subscriptions.update((mutableSubs) => { numberRemovedSubscriptions = mutableSubs.removeUnnamed(); });
API 参考
删除所有订阅
要从订阅集删除所有订阅,请在订阅集上执行事务。在写事务中调用 MutableSubscriptionSet.removeAll()。
realm.subscriptions.update((mutableSubs) => { mutableSubs.removeAll(); });
性能考虑因素
API 效率
使用订阅查询部分中描述的 subscribe()
和 unsubscribe()
API 管理多个订阅的效率低于手动管理订阅时执行批量更新的效率。
为了在进行多项订阅更改时获得更好的性能,请使用subscriptions
API 在单个事务中更新所有订阅。 要了解如何操作,请参阅手动管理订阅。
更新群组以提高性能
订阅集的每个写入事务都会产生性能成本。如果需要在会话期间对 Realm 对象进行多次更新,请考虑将编辑的对象保留在内存中,直到所有更改完成。这通过仅将完整且更新的对象写入 Realm 而不是每次更改来提高同步性能。
“灵活同步”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 错误。