Docs Menu

Docs HomeDevelop ApplicationsAtlas Device SDK

Handle Sync Errors - Flutter SDK

On this page

  • Compensating Write Errors
  • Client Reset
  • Client Reset Modes
  • Automatic vs. Manual Client Reset
  • Client Reset with Recovery
  • Recover or Discard Unsynced Changes Mode
  • Recover Unsynced Changes Mode
  • Discard Unsynced Changes Mode
  • Manual Recovery Fallback
  • Manual Recovery Mode
  • Test Client Reset Handling

While developing an application that uses Device Sync, you should set an error handler. This error handler will detect and respond to any failed sync-related API calls.

Add a syncErrorHandler property to the FlexibleSyncConfiguration when creating a synced realm. syncErrorHandler is a SyncErrorHandler callback function. SyncErrorHandler accepts a SyncError as a parameter. Whenever a SyncError occurs in the realm, the callback function is invoked with the SyncError as its argument.

final config = Configuration.flexibleSync(currentUser, [Car.schema],
syncErrorHandler: (SyncError error) {
print("Error message" + error.message.toString());
});
final realm = Realm(config);

If you do not specify a syncErrorHandler, the default behavior is to print the SyncError to the console.

Tip

For a list of common Device Sync errors and how to handle them, refer to Sync Errors in the App Services Device Sync documentation.

As of Realm Flutter SDK v1.3, you can get detailed information about compensating write errors.

void handleCompensatingWrite(
CompensatingWriteError compensatingWriteError) {
final writeReason = compensatingWriteError.compensatingWrites!.first;
print("Error message: " + writeReason.reason);
// ... handle compensating write error as needed.
}
final config = Configuration.flexibleSync(currentUser, [Car.schema],
syncErrorHandler: (syncError) {
if (syncError is CompensatingWriteError) {
handleCompensatingWrite(syncError);
}
});
final realm = Realm(config);

For more details, refer to the CompensatingWriteError class reference documentation.

When using Device Sync, a client reset is an error recovery task that your client app must perform when the Device Sync server can no longer sync with the client realm.

Clients in this state may continue to run and save data locally but cannot send or receive sync changesets until they perform a client reset. The client must reset its realm to a state that matches the server in order to restore the ability to sync. The unsyncable realm on the client may contain data that has not yet synced to the server.

The Realm SDK can attempt to recover or discard that data during the client reset process. The Realm SDK provides methods to automatically handle client resets in most scenarios.

For more information about what can cause a client reset to occur, go to Client Resets in the App Services documentation.

To manage the client reset process, you can specify a client reset mode in your FlexibleSyncConfiguration.clientResetHandler property when configuring a realm. You can use the following client reset modes:

  • Recover or discard unsynced changes mode (default): In this client reset mode, the client reset handler first attempts to recover unsynced changes. If recovery fails, this handler falls back to the discard unsyced changes mode, which deletes all unsynced local changes. If discard unsyced changes mode fails, the handler falls back to manual recovery mode.

  • Recover unsynced changes mode: In this client reset mode, the client reset handler first attempts to recover unsynced changes. If recovery fails, this handler falls back to manual recovery mode.

  • Discard unsynced changes mode: This client reset mode permanently deletes all local unsynced changes made since the last successful sync. If recovery fails, this handler falls back to manual recovery mode.

  • Manual recovery mode: This client reset mode provides a way for you to implement your own recovery strategy.

The following sections describe how to use these client reset modes.

The Realm SDKs provide client reset modes that automatically handle most client reset errors.

Automatic client reset modes restore your local realm file to a syncable state without closing the realm or missing notifications. The following client reset modes support automatic client resets:

  • Recover unsynced changes mode

  • Recover or discard unsynced changes mode

  • Discard unsynced changes mode

The differences between these modes are based on how they handle changes on the device that have not yet synced to the backend. Only manual recovery mode does not perform an automatic client reset.

Choose recover unsynced changes mode to handle most client reset scenarios automatically. This attempts to recover unsynced changes when a client reset occurs.

If your app requires specific client reset logic that can't be handled automatically, you may want or need to add a manual client reset handler to the automatic client reset mode.

Client Recovery is a feature that is enabled by default when you configure Device Sync. When Client Recovery is enabled, Realm automatically manages the client reset process in most cases. When you make schema changes the client can recover unsynced changes when there are no schema changes, or non-breaking schema changes.

To use Client Recovery, configure your realm with recover unsynced changes or recover or discard unsynced changes client reset modes.

When Client Recovery is enabled, these rules determine how objects are integrated, including how conflicts are resolved when both the backend and the client make changes to the same object:

  • Objects created locally that were not synced before client reset are synced.

  • If an object is deleted on the server, but is modified on the recovering client, the delete takes precedence and the client discards the update.

  • If an object is deleted on the recovering client, but not the server, then the client applies the server's delete instruction.

  • In the case of conflicting updates to the same field, the client update is applied.

For more information about configuring Client Recovery, refer to Client Recovery in the App Services documentation.

Client Recovery cannot occur when your app makes breaking schema changes. A breaking change is a change that you can make in your server-side schema that requires additional action to handle. In this scenario, client reset with fall back to a manual error client reset fallback.

For information on breaking vs. non-breaking schema changes, refer to Breaking vs. Non-Breaking Change Quick Reference in the App Services documentation.

Recover or discard unsynced changes mode attempts to recover all unsynced local changes automatically during a client reset. If you do not specify a client reset mode, client reset behavior defaults to recover or discard unsynced changes. If the automatic recovery process fails, it falls back to discard unsynced changes mode. If that process fails, it falls back again to a manual reset mode.

Recover or discard unsynced changes mode provides the most robust recovery process. However, do not use recover or discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To customize usage of recover or discard unsynced changes mode, pass a RecoverOrDiscardUnsyncedChangesHandler callback to the Configure.clientResetHandler. Add the following optional callback methods to extend the handler's functionality:

  • onBeforeReset, which the SDK invokes prior to the client reset. You can use this callback to notify the user before the client reset begins.

  • onAfterRecovery, which the SDK invokes if and only if the automatic reset completes successfully. You can use it to notify the user that the client reset is complete.

  • onAfterDiscard, which the SDK invokes only if the automatic client reset fails and the discard local strategy succeeds. If the discard strategy fails, this callback is not invoked.

  • onManualResetFallback, which the SDK invokes only if the automatic recovery and the discard strategy have failed. Implement this callback to handle the reset failure, as explained in the Manual Recovery Fallback section.

The following example shows using RecoverOrDiscardUnsyncedChangesHandler and each of its callbacks:

final config = Configuration.flexibleSync(currentUser, schema,
clientResetHandler: RecoverOrDiscardUnsyncedChangesHandler(
// All the following callbacks are optional
onBeforeReset: (beforeResetRealm) {
// Executed before the client reset begins.
// Can be used to notify the user that a reset is going
// to happen.
},
onAfterRecovery: (beforeResetRealm, afterResetRealm) {
// Executed if and only if the automatic recovery has succeeded.
},
onAfterDiscard: (beforeResetRealm, afterResetRealm) {
// Executed if the automatic recovery has failed
// but the discard unsynced changes fallback has completed
// successfully.
},
onManualResetFallback: (clientResetError) {
// Automatic reset failed. Handle the reset manually here.
// Refer to the "Manual Client Reset Fallback" documentation
// for more information on what you can include here.
},
));

Recover unsyced changes mode attempts to recover all unsynced local changes automatically during a client reset. However, unlike recover or discard unsyced changes mode, this mode does not fall back to a discard local changes if the automatic recovery fails. Instead, it falls back to manually recover changes.

To use recover unsynced changes mode, pass a RecoverUnsyncedChangesHandler callback to the Configure.clientResetHandler. This handler provides the following callback methods:

  • onBeforeReset, which the SDK invokes prior to the client reset. You can use this callback to notify the user before the reset begins.

  • onAfterReset, which the SDK invokes if and only if the automatic reset completes successfully. You can use this callback to notify the user when the reset has completed successfully.

  • onManualResetFallback, which the SDK invokes only if the automatic recovery and the discard strategy have failed. You implement this callback to handle the reset failure, as explained in the Manual Recovery Fallback section.

The following example shows using RecoverUnsyncedChangesHandler and each of its callbacks:

final config = Configuration.flexibleSync(currentUser, schema,
clientResetHandler: RecoverUnsyncedChangesHandler(
// All the following callbacks are optional
onBeforeReset: (beforeResetRealm) {
// Executed before the client reset begins.
// Can be used to notify the user that a reset is going
// to happen.
},
onAfterReset: (beforeResetRealm, afterResetRealm) {
// Executed after the client reset is complete.
// Can be used to notify the user that the reset is done.
},
onManualResetFallback: (clientResetError) {
// Automatic reset failed. Handle the reset manually here.
// Refer to the "Manual Client Reset Fallback" documentation
// for more information on what you can include here.
},
));

Discard unsyced changes mode permanently deletes all local unsynced changes made since the last successful sync. If you choose to use this client reset mode, the SDK restores your local realm file to a syncable state without closing the realm and while keeping notifications fully working. If this process fails, it falls back to manual recovery mode.

Do not use recover or discard unsynced changes mode if your application cannot lose local data that has not yet synced to the backend.

To use discard unsynced changes mode, pass a DiscardUnsyncedChangesHandler callback to the Configure.clientResetHandler. This handler provides the following callback methods:

  • onBeforeReset, which the SDK invokes prior to the client reset. You can use this callback to notify the user before the reset begins.

  • onAfterReset, which the SDK invokes if and only if the reset completes successfully. You can use it to notify the user when the reset is complete.

  • onManualResetFallback, which the SDK invokes only if the automatic recovery and the discard strategy have failed. You implement this callback to handle the reset failure, as explained in the Manual Recovery section.

The following example shows using DiscardUnsyncedChangesHandler and each of its callbacks:

final config = Configuration.flexibleSync(currentUser, schema,
clientResetHandler: DiscardUnsyncedChangesHandler(
onBeforeReset: (beforeResetRealm) {
// Executed before the client reset begins.
// Can be used to notify the user that a reset is going
// to happen.
},
onAfterReset: (beforeResetRealm, afterResetRealm) {
// Executed after the client reset is complete.
// Can be used to notify the user that the reset is done.
},
onManualResetFallback: (clientResetError) {
// Automatic reset failed. Handle the reset manually here.
// Refer to the "Manual Client Reset Fallback" documentation
// for more information on what you can include here.
},
));

If the client reset with recovery cannot complete automatically, like when there are breaking schema changes, the client reset process falls through to a manual error handler. This may occur in any of the automatic client reset modes:

  • Recover unsynced changes mode

  • Recover or discard unsynced changes mode

  • Discard unsyced changes mode

You must provide a manual client reset implementation in the onManualResetFallback callback of the client reset handler for these modes.

In onManualResetFallback, initiate a client reset using the ClientResetError.resetRealm() method of the ClientResetError parameter of the callback. ClientResetError.resetRealm() performs a client reset by deleting the realm on device and downloading relevant data from the server. Before you use this method, you must close all instances of the realm that you are resetting.

The following example demonstrates how you can manually handle an error case by discarding all unsynced changes:

// Lazily initialize `realm` so that it can be used in the callback
// before the realm has been opened.
late Realm realm;
final config = Configuration.flexibleSync(currentUser, schema,
// This example uses the `RecoverOrDiscardUnsyncedChangesHandler`,
// but the same logic could also be used with the `RecoverUnsyncedChangesHandler`
// or the `DiscardUnsyncedChangesHandler`.
clientResetHandler: RecoverOrDiscardUnsyncedChangesHandler(
onManualResetFallback: (clientResetError) {
// Prompt user to perform a client reset immediately. If they don't,
// they won't receive any data from the server until they restart the app
// and all changes they make will be discarded when the app restarts.
var didUserConfirmReset = showUserAConfirmationDialog();
if (didUserConfirmReset) {
// You must close the Realm before attempting the client reset.
realm.close();
// Attempt the client reset.
try {
clientResetError.resetRealm();
// Navigate the user back to the main page or reopen the
// the Realm and reinitialize the current page.
} catch (err) {
// Reset failed.
// Notify user that they'll need to update the app
}
}
},
));

Use manual recovery mode for the infrequent cases where you need to customize your data recovery process. In most cases, you should use one of the other strategies for client resets. You might want to use a manual client reset handler if the Automatic Recovery logic does not work for your app and you can't discard unsynced local data.

To use manual recovery mode, pass a ManualRecoveryHandler callback to the Configure.clientResetHandler.

The handler provides the onManualReset callback, in which you can perform the client reset. In onManualReset, initiate a client reset using the ClientResetError.resetRealm() method of the ClientResetError parameter of the callback. ClientResetError.resetRealm() performs a client reset by deleting the realm on device and downloading relevant data from the server. Before you use this method, you must close all instances of the realm that you are resetting.

// Lazily initialize `realm` so that it can be used in the callback
// before the realm has been opened.
late Realm realm;
final config = Configuration.flexibleSync(currentUser, schema,
clientResetHandler: ManualRecoveryHandler((clientResetError) {
// You must close the Realm before attempting the client reset.
realm.close();
// Handle manual client reset here...
// Then perform the client reset.
clientResetError.resetRealm();
}));

You can manually test your application's client reset handling by terminating and re-enabling Device Sync.

When you terminate and re-enable Sync, clients that have previously connected with Sync are unable to connect until after they perform a client reset. Terminating Sync deletes the metadata from the server that allows the client to synchronize. The client must download a new copy of the realm from the server. The server sends a client reset error to these clients. So, when you terminate Sync, you trigger the client reset condition.

To test client reset handling:

  1. Write data from a client application and wait for it to synchronize.

  2. Terminate and re-enable Device Sync.

  3. Run the client app again. The app should get a client reset error when it tries to connect to the server.

Warning

While you iterate on client reset handling in your client application, you may need to terminate and re-enable Sync repeatedly. Terminating and re-enabling Sync renders all existing clients unable to sync until after completing a client reset. To avoid this in production, test client reset handling in a development environment.

←  Sync Data from Multiple ProcessesSet the Client Log Level - Flutter SDK →