Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Is there anyway to access store from inside of an Isolate in flutter? #93

Open
shashikantx opened this issue Nov 22, 2019 · 12 comments
Open

Comments

@shashikantx
Copy link

shashikantx commented Nov 22, 2019

I have been trying to implement the data sync in flutter app, but doing it from future is very inefficient, So I thought I could use isolates to do so, but the app fails to run throwing this exception

Exception has occurred.
FlutterError (ServicesBinding.defaultBinaryMessenger was accessed before the binding was initialized.
If you're running an application and need to access the binary messenger before `runApp()` has been called (for example, during plugin initialization), then you need to explicitly call the `WidgetsFlutterBinding.ensureInitialized()` first.
If you're running a test, you can call the `TestWidgetsFlutterBinding.ensureInitialized()` as the first line in your test's `main()` method to initialize the binding.)

this is the code isolate has

class PersonaSyncMessage {
  final SendPort sendPort;

  PersonaSyncMessage(this.sendPort);
}

void personaSync(PersonaSyncMessage personaSync) async {
  Database db = await AppDatabase.instance.database;

  final store = intMapStoreFactory.store("personas");
  var personas = await store.find(db,
      finder: Finder(filter: Filter.equals("synced", false)));
}

Can I do anything to make it work ? Or is there a way to optimize this process?

@shashikantx shashikantx changed the title Is there anyway to access store from inside of an Isolate in flutter. Is there anyway to access store from inside of an Isolate in flutter? Nov 22, 2019
@alextekartik
Copy link
Collaborator

At first, the issue you get has nothing to do with sembast. As stated in the warning make sure you call WidgetsFlutterBinding.ensureInitialized() in your main first.

That is said. I would not recommend using sembast in a isolate. Sembast is not cross isolate safe so basically you would not even be able to read from your main isolate....

Also I don't think you would get any speed benefit.

@shashikantx
Copy link
Author

shashikantx commented Nov 22, 2019

I already did this

void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  runApp(App());
}

I still get the same exception.

I also want to be able to sync data using service, would that be possible?

Edit:
Also the syncing process would take minute sometimes, so I thought its better run isolate than future.

@alextekartik
Copy link
Collaborator

Weird indeed...Not sure what you mean by service but i think my response will likely be: no ;) you have to use sembast in a single isolate.

@shashikantx
Copy link
Author

shashikantx commented Nov 22, 2019

Yes very weird.

Service as in android native services.

Alright then, thank you for your quick responses and help.

@deakjahn
Copy link

@shashikantx what you saw had nothing to do with Sembast apparently, it's a limitation of Flutter: isolates are not built to use platform channels. While the warning of Alex about Sembast not being isolate-safe is important to heed, if you want to work around that limitation in other scenarios, consider using https://pub.dev/packages/isolate_handler . I use it for something else with success.

@shashikantx
Copy link
Author

@deakjahn hi,

Yes, thank you, I figured that out earlier,

Also another issue I have run into while using isolate_handler is once I initiate Sembast from isolate, I cannot access db from main isolate. Flutter is kinda hard to make background tasks make work.

@deakjahn
Copy link

That I wouldn't know, I only started using Sembast two days ago :-), I was on Sqflite earlier and I'm making the move now. So my isolate platform communications are in a different department, doing uploading of larger files and isolate_handler works wonders there.

@lvlrSajjad
Copy link

lvlrSajjad commented Oct 11, 2020

How about initiating sembast on each isolate :-D For example when I wanna save my data
I use this code:

 initSaveCachedUsersListIsolate() async {
    try {
      Completer completer = new Completer<SendPort>();
      ReceivePort isolateToMainStream = ReceivePort();

      Isolate myIsolateInstance = await Isolate.spawn(
          saveCachedUsersListIsolate, isolateToMainStream.sendPort);

      isolateToMainStream.listen((data) {
        if (data is SendPort) {
          SendPort mainToIsolateStream = data;
          completer.complete(mainToIsolateStream);
        } else {
          if(data == 'done') {
            /// it's done
            myIsolateInstance.kill();
          }
        }
      });

      return completer.future;
    } catch (e) {
      print(e);
    }
  }

  static saveCachedUsersListIsolate(SendPort isolateToMainStream) async {
    try {

      ReceivePort mainToIsolateStream = ReceivePort();
      isolateToMainStream.send(mainToIsolateStream.sendPort);

      mainToIsolateStream.listen((inputData) async {


        List<PMXUser> userList = inputData['userList'];
        String prefix = inputData['prefix'];
        String mode = inputData['mode'];
        String dbPath = inputData['dbPath'];
        Database db = await databaseFactoryIo.openDatabase(dbPath);

        List<Map<String, dynamic>> userMapList = List<Map<String, dynamic>>();

        for (PMXUser pmxUser in userList) {
          userMapList.add(pmxUser.toJson(timeStamp: false));
        }

        StoreRef<dynamic, dynamic> store =
        intMapStoreFactory.store(prefix + mode.toLowerCase());

        await store.addAll(db, userMapList);
        db.close();
        isolateToMainStream.send('done');
      });
    } catch (e) {
//      print(e);
    }
  }

Also, I init my Sembast instance in the mainstream separately on the splash screen as a static variable ... :-D

I think as long as we use one of the Sembast instances (main or isolate) at a time there won't be a problem (especially when we are writing on different collections)

But I think when we have simultaneous read/writes in the same collection there might be a risk of bug using my method (an isolate for each read/writes with a new Sembast instance in that isolate) :-D.

@alextekartik As the developer of this plugin, what do you say? am I right? :-D

Well at least it worked for me ( the writing part ) Now I'm gonna use the same strategy for reading :-D
Because In my app when I have cached data (and it's not expired) I read them otherwise I write so I don't have simultaneous read/writes in the same collection.
I don't think there will be a risk. :-D

Update: Reading also worked with above strategy

@alextekartik
Copy link
Collaborator

alextekartik commented Oct 11, 2020

@lvlrSajjad You will also need a cross isolate mutex/lock to ensure that open/do_action/close are not ran at the same time. Performance will be poor and you cannot keep your database open in the main isolate (changes from the other isolates will be ignored). Currently I don't see an easy solution (besides having a single isolate handling database access and other isolates "talking" with this isolate).

@lvlrSajjad
Copy link

@alextekartik I agree with your last solution. Having a single isolate for the database is a good solution.

@lvlrSajjad
Copy link

Finally, I decided to use Sembast in the main isolate (Like normal) and use isolates for pre/post data processing instead. Because there were issues even with a single isolate (other than the main isolate).

@alextekartik
Copy link
Collaborator

@lvlrSajjad Good choice! Running from another isolate is not tested/recommended at this point.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants