Docs Menu

Docs HomeDevelop ApplicationsAtlas Device SDKs

Write Data to a Synced Realm - Flutter SDK

On this page

  • Determining What Data Syncs
  • App Services Configuration
  • Client Data Model and Configuration
  • Write to a Synced Realm
  • Successful Writes
  • Compensating Writes
  • Writes that Don't Match the Query Subscription
  • Writes That Don't Match Permissions

When writing data to a synced realm using Flexible Sync, you can use the same APIs as writing to a local realm. However, there are some differences in behavior to keep in mind as you develop your application. To learn more about reading and writing data to a realm, refer to Read & Write Data.

When you write to a synced realm, your write operations must match both of the following:

  • The sync subscription query.
    • If your write operation doesn't match the query in the subscription, the write reverts with a non-fatal compensating write error (ErrorCompensatingWrite).

    • To learn more about compensating write errors and how to avoid them, refer to the Compensating Writes section.

  • The permissions in your App Services App.
    • If your try to write data that doesn't match the permissions expression, the write reverts with a non-fatal permission denied error. In the client, this shows as an error (ErrorCompensatingWrite). On the server, you can see more details about how the write was denied was by a write filter in the role.

    • To learn more about configuring permissions for your app, see Role-based Permissions and the Device Sync Permissions Guide in the App Services documentation.

To learn more about permission denied errors, compensating write errors and other Device Sync error types, refer to Sync Errors in the App Services documentation.

The examples on this page use an Atlas App Services App with the following Device Sync configuration and a client app with the following Realm SDK data model and subscriptions.

Device Sync is configured with the following queryable fields:

  • _id (always included)

  • miles

  • ownerId

The App Services App has permissions configured to let users read and write only their own data:

{
"name": "owner-read-write",
"apply_when": {},
"document_filters": {
"read": { "ownerId": "%%user.id" },
"write": { "ownerId": "%%user.id" }
},
"read": true,
"write": true
}

The examples on this page use the following schema:

@RealmModel()
class _Car {
@MapTo("_id")
@PrimaryKey()
late ObjectId id;
// This is the queryable field
late String ownerId;
late String make;
late String? model;
late int? miles;
}

Using that schema, the examples configure the synced realm to synchronize objects matching this subscription query:

final app = App(AppConfiguration(APP_ID));
final user = await app.logIn(Credentials.anonymous());
final config = Configuration.flexibleSync(user, [Car.schema]);
final realm = Realm(config);
// Add subscriptions
realm.subscriptions.update((mutableSubscriptions) {
// Get Cars from Atlas that match the Realm Query Language query.
// Uses the queryable field `miles`.
// Query matches cars with less than 100 miles or `null` miles.
final newCarQuery = realm.query<Car>("miles < 100 OR miles == \$0", [null]);
mutableSubscriptions.add(newCarQuery, name: "new-car-subscription");
});
await realm.subscriptions.waitForSynchronization();

Writes to Flexible Sync realms may broadly fall into one of two categories:

  • Successful writes: The written object matches both the query subscription and the user's permissions. The object writes successfully to the realm, and syncs successfully to the App Services backend and other devices.

  • Compensating writes: When the written object does not match the subscription query, or where the user does not have sufficient permissions to perform the write, Realm reverts the illegal write.

When the write matches both the App Services permissions and the Flexible Sync subscription query in the client, the Realm Flutter SDK can successfully write the object to the synced realm. This object syncs with the App Services backend when the device has a network connection.

// Per the Device Sync permissions, users can only read and write data
// where the `Car.ownerId` property matches their own user ID.
final userId = user.id;
realm.write(() {
// WRITE SUCCEEDS
// `newCar` is successfully written to the realm and synced to Atlas
// because it's data matches the subscription query (miles < 100)
// and it's `ownerId` field matches the user ID.
final newCar = Car(ObjectId(), userId, 'Toyota', miles: 2);
realm.add(newCar);
});

In some cases, a write that initially appears to succeed is actually an illegal write. In these cases, the object writes to the database, but when the database syncs to the backend, Realm reverts the write in a non-fatal error operation called a compensating write. Compensating writes can occur when:

  • Writes don't match the query subscription: The written object matches the user's permissions, but does not match the query subscription.

  • Writes don't match permissions: The written object matches the query subscription, but does not match the user's permissions.

In more detail, when you write data that is outside the bounds of a query subscription or does not match the user's permissions, the following occurs:

  1. Because the client realm has no concept of "illegal" writes, the write initially succeeds until realm resolves the changeset with the App Services backend.

  2. Upon sync, the server applies the rules and permissions. The server determines that the user does not have authorization to perform the write.

  3. The server sends a revert operation, called a "compensating write", back to the client.

  4. The client's realm reverts the illegal write operation.

Any client-side writes to a given object between an illegal write to that object and the corresponding compensating write will be lost.

In practice, this may look like an object being written to the realm, and then disappearing after the server sends the compensating write back to the client.

To learn more about permission denied errors, compensating write errors and other Device Sync error types, refer to Sync Errors in the App Services documentation.

The App Services logs contain more information about why a compensating write error occurs.

You can only write objects to a Flexible Sync realm if they match the subscription query. If you perform a write that does not match the subscription query, Realm initially writes the object, but then performs a compensating write. This is a non-fatal operation that reverts an illegal write that does not match the subscription query.

If you want to write an object that does not match the query subscription, open a different realm where the object matches the query subscription. Alternately, you could write the object to a local realm that does not enforce permissions or subscription queries.

Given the configuration for the synced realm above, attempting to write this object does not match the query subscription:

final carId = ObjectId();
final ownerId = app.currentUser!.id;
realm.write(() {
// WRITE REVERTED BY QUERY SUBSCRIPTION COMPENSATING WRITE
// `oldCar` is initially written to the realm, then later removed
// in a compensating write when the server processes the write.
// This is because the `miles` property of `oldCar` doesn't match
// the subscription query, which is only for cars with less than 100 miles.
final oldCar = Car(carId, ownerId, 'Honda', miles: 90000);
realm.add(oldCar);
});
// Let changes sync to and from server
await realm.syncSession.waitForUpload();
await realm.syncSession.waitForDownload();
final noCar = realm.find<Car>(carId);
// The Car is no longer in the realm because of
// the compensating write from the server.
expect(noCar, isNull);

The error message in the client-side logs in this scenario is:

[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted
a write that is outside of permissions or query filters; it has been reverted"
(error_code=231, try_again=true, error_action=Warning)
[INFO] Realm: Connection[1]: Session[1]: Reporting compensating write
for client version 21 in server version 2877: Client attempted a write that
is outside of permissions or query filters; it has been reverted
[ERROR] Realm: SyncSessionError message: Client attempted a write that
is outside of permissions or query filters; it has been reverted
Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c
category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false

The error message in the App Services logs in this scenario is:

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

Attempting to write to the client can also trigger a compensating write error when the object does not match the user's server-side write permissions.

On the client, this type of write behaves the same as a write that doesn't match the query subscription. In practice, this may look like the write succeeding, but then the object "disappears" when Realm syncs with the App Services backend and performs the compensating write.

Given the permissions in the Device Sync configuration detailed above, attempting to write an object where the ownerId property does not match the user.id of the logged-in user is not a legal write:

final carId = 'someOtherId';
realm.write(() {
// WRITE REVERTED BY PERMISSION ERROR
// `otherUsersCar` is initially written to the realm, then removed upon synchronization
// because it's `ownerId` property doesn't match the user ID of the user
// making the request.
final otherUsersCar = Car(ObjectId(), carId, 'Ford');
realm.add(otherUsersCar);
});
// sync changes
await realm.syncSession.waitForUpload();
await realm.syncSession.waitForDownload();
final noCar = realm.find<Car>(carId);
// The Car is no longer in the realm because of
// the compensating write from the server.
expect(noCar, isNull);

The client error in this scenario is the same as when you attempt to write an object that is outside the App Services permissions:

[INFO] Realm: Connection[1]: Session[1]: Received: ERROR "Client attempted
a write that is outside of permissions or query filters; it has been reverted"
(error_code=231, try_again=true, error_action=Warning)
[INFO] Realm: Connection[1]: Session[1]: Reporting compensating write
for client version 25 in server version 2879: Client attempted a write that
is outside of permissions or query filters; it has been reverted
[ERROR] Realm: SyncSessionError message: Client attempted a write that
is outside of permissions or query filters; it has been reverted
Logs: https://services.cloud.mongodb.com/groups/5f60207f14dfb25d23101102/apps/639340a757271cb5e3a0f0cf/logs?co_id=6424433efb0c6bbcc330347c
category: SyncErrorCategory.session code: SyncSessionErrorCode.compensatingWrite isFatal: false

The error message in the App Services logs provides some additional information to help you determine that it is a permissions issue, and not a query subscription issue. In this example, the error message shows that the the object does not match the user's role:

Error:
Client attempted a write that is outside of permissions or query filters;
it has been reverted (ProtocolErrorCode=231)
Details:
{
"Car": {
"6424433fd4d9f52ee93ad591": "write to \"6424433fd4d9f52ee93ad591\" in table \"Car\" was denied by write filter in role \"owner-read-write\""
}
}
← Manage Sync Subscriptions - Flutter SDK