objectbox / objectbox-dart

Flutter database for super-fast Dart object persistence
https://docs.objectbox.io/getting-started
Apache License 2.0
1.05k stars 120 forks source link

Issue with Store.attach() throwing UnsupportedError on iOS physical device #653

Open edrbtsyn opened 4 months ago

edrbtsyn commented 4 months ago

Hello,

I am using the objectbox-dart library in my Flutter application, which also utilizes Firebase Cloud Messaging for background notifications. When handling notifications in the background on a physical iOS device, I encounter an exception when calling the Store.attach() method. This issue does not occur on an Android emulator.

Here is how I use the library:

Future<String> getDatabaseDirectoryPath() async {
  final dir = await getApplicationDocumentsDirectory();
  return '${dir.path}${Platform.pathSeparator}objectbox';
}
static Future<Store> tryOpenStore(String dbPath,
      {required int maxRetries}) async {
    try {
      if (maxRetries > 0) {
        return await openStore(directory: dbPath);
      } else {
        const exceptionMessage =
            "Couldn't open store because of recurring SchemaExceptions.";
        globalLogger.i(exceptionMessage);
        throw Exception(exceptionMessage);
      }
    } on SchemaException catch (_) {
      maxRetries--;
      await deleteDatabaseFiles(dbPath);
      return await tryOpenStore(dbPath, maxRetries: maxRetries);
    }
}
static Future<Store> createStore() async {
  final dbPath = await getDatabaseDirectoryPath();
  final isOpen = Store.isOpen(dbPath);
  if (isOpen) {
    final store = Store.attach(getObjectBoxModel(), dbPath);
    return store;
  } else {
    final store = await tryOpenStore(dbPath, maxRetries: 2);
    return store;
  }
}

The crash occurs in an event triggered when the application is in the background (receiving a Firebase notification):

@pragma('vm:entry-point')
Future<void> _firebaseMessagingBackgroundHandler(RemoteMessage message) async {
  // Initialize some stuff here…
  final store = await RubisObjectBox.createStore(); // works on Android, not on iOS
  MessagingHelper.handleMessage(
    DataMessage.fromJson(message.data), message.data, store, runFromBackground: true
  );
}

On iOS, the Store.attach() method throws an UnsupportedError with the message:

Cannot create multiple Store instances for the same directory in the same isolate. Please use a single Store, close() the previous instance before opening another one or attach to it in another isolate.

I tried using Isolate.run to return the store, but it seems not possible since the getApplicationDocumentsDirectory() method cannot be called within an isolate.

Environment:

Steps to reproduce:

Any guidance or solution for this issue would be greatly appreciated. Thank you!

greenrobot-team commented 4 months ago

As the message says, it is not supported to attach to a store that is already opened in the current isolate. The code should either keep a reference to an open store and re-use that. Or make sure to close it before opening/attaching it again.

Note: labeled this issue with "more info required" so it will auto-close in a few days if there are no follow-up comments.

edrbtsyn commented 4 months ago

I made some tests on the library, and observed that when Store.isOpen() returns true, the _openStoreDirectories (a HashSet) used in the library method _checkStoreDirectoryNotOpen is not empty, so it does not throw an exception. However, on iOS, when Store.isOpen() returns true, the _openStoreDirectories variable is NOT empty, causing the UnsupportedError exception to be thrown.

The latter seems to be the intended behavior, but isn't it a bug on the Android side ?

greenrobot-team commented 4 months ago

Thanks for asking. This is indeed a little confusing. The Store.isOpen() check works on the process level (you can see it calls a C API), so across Dart isolates. The open directories check when creating/attaching a Store works per isolate (as no Dart state is shared across isolates).

Maybe we should update the API docs to clarify this.