/flutter_isolate_web

Unified interface to isolates and web workers.

Primary LanguageDartMIT LicenseMIT

Flutter Isolate Web

The title is a misnomer. Of course, there are no isolates in Flutter Web. What this code provides is a unified interface to isolates and web workers so that each platform can use its own. It's not a package on pub.dev and it won't be because you can't use it out of the box just like a regular package or plugin. You have to copy it into your own code and modify it to suit your needs.

Dependencies

It depends on:

  • isolate_handler, this is what provides the isolates with communication already in place,

  • js, this is what provides the connection to JavaScript.

Usage

Create a worker first:

final worker = BackgroundWorker();

and start it when needed:

worker.spawn(
  doWork,
  name: 'some-unique-name',
  onInitialized: onInitialized,
  onFromWorker: onReceive,
);

You can start any amount of workers, just give a unique name to all so that you can reference them later when sending or receiving messages.

doWork() is a function taking a Map argument (the context of the isolate/worker). As customary with standard isolates, it has to be a top-level or static function. The most usual activity here is to start listening to messages the isolate/worker will receive from the main app (the actual message structure is completely up to you, this is just an example):

void doWork(Map<String, dynamic> context) {
  worker.listen((args) {
    switch (args['command']) {
      case 'start':
        // worker starts its job
        break;
    }
  }, context: context);
}

When the isolate/worker actually gets initialized, the main app will be notified. You might simply use this to send a message back to the worker to start the actual work (the actual message structure is completely up to you, this is just an example):

void onInitialized() {
  worker.sendTo('some-unique-name', {
    // tell the worker to start its job
    'command': 'start',
    'data': ...,
  });
}

There is an important difference between the two that must be understood. doWork() runs in the worker/isolate, this is the main entry point of the worker/isolate code. onInitialized and onFromWorker run in the main app, this is where the main app receives messages from the workers/isolates.

This second is the main messaging mechanism. Make sure the isolate/worker also knows the main worker object and its own unique name (the name was returned to the entryPoint() as context['name']) because it needs those to send its messages back:

// isolate/worker sends to main app:
worker.sendFrom('unique-name', message);

// main app sends to isolate/worker:
worker.sendTo('unique-name', message);

// main app receives from isolate/worker:
void onReceive(T message) {
  //...
}

If you need to kill the worker/isolate, use:

worker.kill('unique-name');

To kill all of them, use the names list:

for (String name in worker.names) worker.kill(name);

Web

Everything described above works on mobile with isolates. However, the Flutter Web side is not that nice yet.

If you have the JavaScript code you want to run when compiling your app for the web, it works. Call your code with the importScripts() in worker_web.dart and that's all. You can even do away with all that createObjectUrlFromBlob stuff and construct the Worker directly with the JS file from your app assets or downloaded from a CDN. The same applies if your isolate code from your mobile app is simple enough so that you can and want to replicate it in JavaScript.

The missing link in the chain is if you want to use the same Dart code you used for your isolate in your web worker. If your code is free from dependencies, you could get away with simply compiling it with:

dart2js --libraries-spec=$HOME/flutter/bin/cache/flutter_web_sdk/libraries.json -o worker.js worker.dart

copying it to your assets and loading it. But we are unable to simply re-use our existing Dart code, even if it is actually compiled to JavaScript, anyway. There's no way to compile part of your app (your isolate/worker) into a separate file and to include it separately for the worker to use, and, by obvious limitation of web workers, we can't simply call into our main app code from the worker, either.

Intermediate solution for the web

The package also has a worker_async.dart file. This is an async solution to the problem. It implements the same interface, so it can be used as is to make the isolate/worker scheme work on the web. It won't be a real web worker, though, it won't run in parallel but it works. These "fake" workers will be called in the usual asnychronous way. While not yet the real McCoy, it's a functional replacement.