realm / realm-dart

Realm is a mobile database: a replacement for SQLite & ORMs.
Apache License 2.0
749 stars 80 forks source link

Advice required #1742

Closed richard457 closed 1 week ago

richard457 commented 4 weeks ago

What happened?

This is not a bug but general advice is needed, Sometimes we encounter a breaking change and it is hard to avoid a breaking change with flexible sync, we tried to implement a reset handler but we would like to hear your advice on our implementation here is our implementation

Future<Configuration> _createPersistentConfig(User user, String path) async {
    return Configuration.flexibleSync(
      user,
      realmModels,
      encryptionKey: ProxyService.box.encryptionKey().toIntList(),
      path: path,
      shouldCompactCallback: (totalSize, usedSize) {
        const tenMB = 10 * 1048576;
        return (totalSize > tenMB) &&
            (usedSize.toDouble() / totalSize.toDouble()) < 0.5;
      },
      syncErrorHandler: (syncError) {
        if (syncError is CompensatingWriteError) {
          handleCompensatingWrite(syncError);
        }
      },
      clientResetHandler: RecoverOrDiscardUnsyncedChangesHandler(
        onBeforeReset: (realm) {
          print(
              'A client reset is about to occur. The app may freeze momentarily.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "A client reset is about to occur. The app may freeze momentarily.'",
            ),
          );
        },
        onAfterRecovery: (before, after) {
          print('Automatic client reset recovery completed successfully.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "Automatic client reset recovery completed successfully.",
            ),
          );
        },
        onAfterDiscard: (before, after) {
          talker.error(
              'Automatic client reset recovery failed. Local changes have been discarded.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "Automatic client reset recovery failed. Local changes have been discarded.",
            ),
          );
        },
        onManualResetFallback: (clientResetError) async {
          print('Automatic client reset failed. Manual reset is required.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  'Automatic client reset failed. Manual reset is required.',
            ),
          );
          await Future.delayed(Duration(seconds: 10));
          // 1. Close the realm
          realm!.close();

          // 2. Delete the realm file
          try {
            File realmFile = File(realm!.config.path);
            if (await realmFile.exists()) {
              await realmFile.delete();
              print('Realm file deleted successfully.');
            }
          } catch (e) {
            print('Error deleting realm file: $e');
          }

          // 3. Restart the app
          print('Restarting the app...');
          exit(0);

          //the app re-initialize here
        },
      ),
    );
  }

Repro steps

Future<Configuration> _createPersistentConfig(User user, String path) async {
    return Configuration.flexibleSync(
      user,
      realmModels,
      encryptionKey: ProxyService.box.encryptionKey().toIntList(),
      path: path,
      shouldCompactCallback: (totalSize, usedSize) {
        const tenMB = 10 * 1048576;
        return (totalSize > tenMB) &&
            (usedSize.toDouble() / totalSize.toDouble()) < 0.5;
      },
      syncErrorHandler: (syncError) {
        if (syncError is CompensatingWriteError) {
          handleCompensatingWrite(syncError);
        }
      },
      clientResetHandler: RecoverOrDiscardUnsyncedChangesHandler(
        onBeforeReset: (realm) {
          print(
              'A client reset is about to occur. The app may freeze momentarily.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "A client reset is about to occur. The app may freeze momentarily.'",
            ),
          );
        },
        onAfterRecovery: (before, after) {
          print('Automatic client reset recovery completed successfully.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "Automatic client reset recovery completed successfully.",
            ),
          );
        },
        onAfterDiscard: (before, after) {
          talker.error(
              'Automatic client reset recovery failed. Local changes have been discarded.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "Automatic client reset recovery failed. Local changes have been discarded.",
            ),
          );
        },
        onManualResetFallback: (clientResetError) async {
          print('Automatic client reset failed. Manual reset is required.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  'Automatic client reset failed. Manual reset is required.',
            ),
          );
          await Future.delayed(Duration(seconds: 10));
          // 1. Close the realm
          realm!.close();

          // 2. Delete the realm file
          try {
            File realmFile = File(realm!.config.path);
            if (await realmFile.exists()) {
              await realmFile.delete();
              print('Realm file deleted successfully.');
            }
          } catch (e) {
            print('Error deleting realm file: $e');
          }

          // 3. Restart the app
          print('Restarting the app...');
          exit(0);

          //the app re-initialize here
        },
      ),
    );
  }

Version

3.1.1

What Atlas Services are you using?

Atlas Device Sync

What type of application is this?

Flutter Application

Client OS and version

windows,macos,android,iPhone

Code snippets

Future<Configuration> _createPersistentConfig(User user, String path) async {
    return Configuration.flexibleSync(
      user,
      realmModels,
      encryptionKey: ProxyService.box.encryptionKey().toIntList(),
      path: path,
      shouldCompactCallback: (totalSize, usedSize) {
        const tenMB = 10 * 1048576;
        return (totalSize > tenMB) &&
            (usedSize.toDouble() / totalSize.toDouble()) < 0.5;
      },
      syncErrorHandler: (syncError) {
        if (syncError is CompensatingWriteError) {
          handleCompensatingWrite(syncError);
        }
      },
      clientResetHandler: RecoverOrDiscardUnsyncedChangesHandler(
        onBeforeReset: (realm) {
          print(
              'A client reset is about to occur. The app may freeze momentarily.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "A client reset is about to occur. The app may freeze momentarily.'",
            ),
          );
        },
        onAfterRecovery: (before, after) {
          print('Automatic client reset recovery completed successfully.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "Automatic client reset recovery completed successfully.",
            ),
          );
        },
        onAfterDiscard: (before, after) {
          talker.error(
              'Automatic client reset recovery failed. Local changes have been discarded.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  "Automatic client reset recovery failed. Local changes have been discarded.",
            ),
          );
        },
        onManualResetFallback: (clientResetError) async {
          print('Automatic client reset failed. Manual reset is required.');
          ProxyService.local.notify(
            notification: AppNotification(
              ObjectId(),
              identifier: ProxyService.box.getBranchId(),
              type: "internal",
              id: randomNumber(),
              completed: false,
              message:
                  'Automatic client reset failed. Manual reset is required.',
            ),
          );
          await Future.delayed(Duration(seconds: 10));
          // 1. Close the realm
          realm!.close();

          // 2. Delete the realm file
          try {
            File realmFile = File(realm!.config.path);
            if (await realmFile.exists()) {
              await realmFile.delete();
              print('Realm file deleted successfully.');
            }
          } catch (e) {
            print('Error deleting realm file: $e');
          }

          // 3. Restart the app
          print('Restarting the app...');
          exit(0);

          //the app re-initialize here
        },
      ),
    );
  }

Stacktrace of the exception/crash you're getting

No response

Relevant log output

No response

sync-by-unito[bot] commented 4 weeks ago

➤ PM Bot commented:

Jira ticket: RDART-1067

nirinchev commented 3 weeks ago

If the breaking schema change is changing something that shipped as part of the app models, then that approach wouldn't work as even after the app restart, the client would attempt to use the old schema which is now incompatible with the server schema. The only solution in that case is to delete the local Realm file and then install a new version of the app that has the correct schema.