Docs 菜单

Docs 主页开发应用程序Atlas Device SDKs

将数据写入同步 Realm - Kotlin SDK

在此页面上

  • 确定同步哪些数据
  • App Services 配置
  • 客户端Realm 数据模型和配置
  • 写入同步 Realm
  • 成功写入
  • 补偿写入
  • 写入与查询订阅不匹配
  • 写入与权限不匹配
  • 补偿写入错误信息
  • 对写入进行分组以提高性能

使用“ Flexible Sync ”将数据写入同步 Realm 时,您可以使用与写入本地 Realm相同的 API。但是,在开发应用程序时,需要记住一些行为差异。

当您写入同步 Realm 时,写入操作必须匹配以下两项

  • 同步订阅查询

  • App Services App 中的权限

如果您尝试写入与查询订阅或用户权限不匹配的数据,Realm 会使用称为补偿写入的非致命错误操作来恢复写入。

要了解有关为应用配置权限的更多信息,请参阅 App Services 文档中的基于角色的权限Device Sync 权限指南

要了解有关权限被拒绝错误、补偿写入错误和其他 Device Sync 错误类型的更多信息,请参阅 App Services 文档中的同步错误

您可以写入同步 Realm 的数据由以下因素决定:

  • Device Sync 配置

  • 应用中的权限

  • 打开 Realm 时使用的 Flexible Sync 订阅查询

本页上的示例使用具有以下 Device Sync 配置的 Atlas App Services App 以及具有以下 Realm 软件开发工具包(Realm SDK) Realm 数据模型和订阅的客户端应用程序。

在此示例中,客户端应用程序使用以下对象模型:

class Item : RealmObject {
@PrimaryKey
var _id: ObjectId = ObjectId()
var ownerId: String = ""
var itemName: String = ""
var complexity: Int = 0
}

根据上述示例对象模型,Device Sync 配置有以下可查询字段:

  • _id (始终包含在内)

  • complexity

  • ownerId

App Services App 的权限配置为仅允许用户读取和写入自己的数据:

{
"roles": [
{
"name": "readOwnWriteOwn",
"apply_when": {},
"document_filters": {
"write": {
"ownerId": "%%user.id"
},
"read": {
"ownerId": "%%user.id"
}
},
"read": true,
"write": true,
"insert": true,
"delete": true,
"search": true
}
]
}

在Atlas collection中的任何对象,如果ownerId与登录用户的user.id不匹配,则无法同步到此 域。

使用对象模型,这些示例将同步 Realm 配置为同步与此订阅查询匹配的对象:

val app = App.create(FLEXIBLE_APP_ID)
val user = app.login(credentials)
val flexSyncConfig = SyncConfiguration.Builder(user, setOf(Item::class))
// Add subscription
.initialSubscriptions { realm ->
add(
// Get Items from Atlas that match the Realm Query Language query.
// Uses the queryable field `complexity`.
// Query matches objects with complexity less than or equal to 4.
realm.query<Item>("complexity <= 4"),
"simple-items"
)
}
.build()
val syncRealm = Realm.open(flexSyncConfig)
syncRealm.subscriptions.waitForSynchronization()
Log.v("Successfully opened realm: ${syncRealm.configuration}")

在Atlas collection中,complexity属性的值大于4的任何对象都无法同步到此域。

对 Flexible Sync Realm的写入大致可以分为以下两类

  • 成功写入:写入的对象同时与查询订阅和用户权限匹配。 该对象成功写入到域,并成功同步到 App Services 后端和其他设备。

  • 补偿写入:写入的对象与订阅查询不匹配,或者用户没有足够的权限来执行写入。 Realm 通过补偿写入操作来恢复非法写入。

提示

如果要写入与查询订阅不匹配的对象,可以打开该对象与查询订阅匹配的另一个域。或者,您可以将对象写入不强制执行权限或订阅查询的非同步 Realm。

当写入与用户权限客户端中的查询订阅匹配时,Realm Kotlin SDK 可以成功将该对象写入同步 Realm。当设备具有网络连接时,此对象与 App Services 后端同步。

// Per the Device Sync permissions, users can only read and write data
// where the `Item.ownerId` property matches their own user ID.
val userId = user.id
val newItem = Item().apply {
ownerId = userId
itemName = "This item meets sync criteria"
complexity = 3
}
syncRealm.write {
// `newItem` is successfully written to the realm and synced to Atlas
// because its data matches the subscription query (complexity <= 4)
// and its `ownerId` field matches the user ID.
copyToRealm(newItem)
}

当写入与查询订阅用户权限不匹配时,Realm 会恢复写入并抛出CompensatingWriteException。

更详细地说,当写入超出查询订阅范围或与用户权限不匹配的数据时,会出现以下情况:

  1. 由于客户端 Realm 没有“非法”写入的概念,因此写入最初会成功,直到 Realm 使用 App Services 后端解析变更集。

  2. 同步后,服务器会应用规则和权限。 服务器确定用户无权执行写入操作。

  3. 服务器向客户端发回恢复操作(称为“补偿写入”)。

  4. 客户端 Realm 恢复非法写入操作。

在对给定对象的非法写入和相应的补偿写入之间,客户端对该对象的任何写入都将丢失。 实际上,这可能看起来像写入成功,但当 Realm 与 App Services 后端同步并执行补偿写入时,该对象“消失”。

发生这种情况时,您可以参考App Services 日志或使用客户端中的CompensatingWriteInfo对象来获取有关该错误的其他信息。

根据上面详细介绍的 Flexible Sync Realm配置,尝试写入此对象会导致补偿写入错误,因为该对象与查询订阅不匹配:

// The complexity of this item is `7`. This is outside the bounds
// of the subscription query, which triggers a compensating write.
val itemTooComplex = Item().apply {
ownerId = user.id
itemName = "This item is too complex"
complexity = 7
}
syncRealm.write {
copyToRealm(itemTooComplex)
}
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.

您将在 App Services 日志中看到以下错误消息:

Error:
Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231)
Details:
{
"Item": {
"63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\"
in table \"Item\" not allowed; object is outside of
the current query view"
}
}

给定上述 Device Sync 配置中的权限,尝试写入此对象会导致补偿写入错误,因为ownerId属性与登录用户的user.id不匹配:

// The `ownerId` of this item does not match the `user.id` of the logged-in
// user. The user does not have permissions to make this write, which
// triggers a compensating write.
val itemWithWrongOwner = Item().apply {
ownerId = "not the current user"
itemName = "A simple item"
complexity = 1
}
syncRealm.write {
copyToRealm(itemWithWrongOwner)
}
[Session][CompensatingWrite(231)] Client attempted a write that is disallowed by permissions, or modifies an object outside the current query, and the server undid the change.

您将在 App Services 日志中看到以下错误消息:

Error:
Client attempted a write that is outside of permissions or query filters; it has been reverted (ProtocolErrorCode=231)
Details:
{
"Item": {
"63bdfc40f16be7b1e8c7e4b7": "write to \"63bdfc40f16be7b1e8c7e4b7\"
in table \"Item\" was denied by write filter in role \"readOwnWriteOwn\""
}
}

1.9.0 版本中的新增功能

您可以使用CompensatingWriteInfo对象在客户端中获取有关为何进行补偿写入的其他信息,该对象提供:

  • 客户端尝试写入的对象的objectType

  • 特定对象的primaryKey

  • 用于补偿写入错误的reason

此信息与您在App Services 日志中找到的信息相同。出于方便和调试目的,Kotlin SDK 在客户端公开此对象。

以下示例说明了如何记录有关补偿写入错误的信息:

val syncErrorHandler = SyncSession.ErrorHandler { session, error ->
runBlocking {
if (error is CompensatingWriteException) {
error.writes.forEach { writeInfo ->
val errorMessage = """
A write was rejected with a compensating write error
The write to object type: ${writeInfo.objectType}
With primary key of: ${writeInfo.primaryKey}
Was rejected because: ${writeInfo.reason}
""".trimIndent()
Log.e(errorMessage)
}
}
}
}
A write was rejected with a compensating write error
The write to object type: Item
With primary key of: RealmAny{type=OBJECT_ID, value=BsonObjectId(649f2c38835cc0346b861b74)}
Was rejected because: write to "649f2c38835cc0346b861b74" in table "Item" not allowed; object is outside of the current query view
  • 此消息中的Item是此页面上的对象模型中使用的Item对象。

  • 主键是客户端尝试写入的特定对象的objectId

  • table "Item"指的是此对象将同步的 Atlas collection。

  • 此示例中的object is outside of the current query view原因是查询订阅设置为要求对象的complexity属性小于或等于4 ,并且客户端尝试写入此边界之外的对象。

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

← 托管同步订阅 - Kotlin SDK