isolate_handler 0.1.1+info

  • Readme
  • Changelog
  • Example
  • Installing
  • 79

Isolate Handler #

Effortless isolates abstraction layer with support* for MethodChannel calls.

Getting Started #

Be aware of limitations #

Accessing MethodChannels from isolates using this package should not be done in a production environment, at least not without full understanding of the limitations. Please read the section titled Limitations first.

What's an isolate? #

In the words of the Dart documentation itself, isolates are:

Independent workers that are similar to threads but don't share memory, communicating only via messages.

In short, Dart is a single-threaded language, but it has support for concurrent execution of code through these so-called isolates.

This means that you can use isolates to execute code you want to run alongside your main thread, which is particularly useful for keeping your Flutter application running smoothly.

For more detailed information, please read this excellent article by Didier Boelens.

Why should I use Isolate Handler? #

Short answer: access* to MethodChannel calls from within isolates in Flutter.

(* Be aware of the limitations.)

Dart already has a very clean interface for spawning and interacting with isolates, using Isolate Handler instead of the regular interface provides only a slightly simpler way of communicating with isolates.

Aside from that and access to isolates through names, the real motivation behind this package was to support access to native code from within an isolate when using as part of Flutter on a mobile device.

Isolate Handler makes this relatively seamless, only requiring a list of channels be provided.

Using Isolate Handler #

Spawning an isolate #

Spawning an isolate with Isolate Handler is really simple:

IsolateHandler().spawn(entryPoint);

This is similar to how isolates are spawned normally, with the exception that Isolate Handler does not expect a message parameter, only an entry point. Messaging has been abstracted away and a communications channel is instead opened automatically.

Communicating with an isolate #

Just spawning an isolate provides no benefit over simply using Isolate.spawn, so let's move on to a slightly more useful example; sending data to isolate and receiving some back.

Let's do a complete project where we start an isolate and send it an integer, have it add one to our count and return the value. We will also give our isolate a name to make it easy to access from anywhere.

final isolates = IsolateHandler();
int counter = 0;

void main() {
  // Start the isolate at the `entryPoint` function.
  isolates.spawn<int>(entryPoint,
    name: "counter",
    // Executed every time data is received from the spawned isolate.
    onReceive: setCounter,
    // Executed once when spawned isolate is ready for communication.
    onInitialized: () => isolates.send(counter, to: "counter")
  );
}

// Set new count and display current count.
void setCounter(int count) {
  counter = count;
  print("Counter is now $counter");
  
  // We will no longer be needing the isolate, let's dispose of it.
  isolates.kill("counter");
}

// This function happens in the isolate.
void entryPoint(HandledIsolateContext context) {
  // Calling initialize from the entry point with the context is
  // required if communication is desired. It returns a messenger which
  // allows listening and sending information to the main isolate.
  final messenger = HandledIsolate.initialize(context);

  // Triggered every time data is received from the main isolate.
  messenger.listen((count) {
    // Add one to the count and send the new value back to the main
    // isolate.
    messenger.send(++count);
  });
}

Call to invokeMethod from isolate

Now that we know how to use Isolate Handler to create and communicate with isolates, let's take a look at how to use it for its main purpose; accessing native calls from within the isolate.

We will modify the code from our previous example a little bit to make it request the new count from native instead of just adding one by itself:

final isolates = IsolateHandler();
int counter = 0;

// Let's store our channels in a top-level Map for convenience.
const Map<String, MethodChannel> channels = {
  'counter': const MethodChannel('isolates.example/counter'),
};

void main() {
  // Start the isolate at the `entryPoint` function.
  isolates.spawn<int>(entryPoint,
    name: "counter",
    // Executed every time data is received from the spawned isolate.
    onReceive: setCounter,
    // Executed once when spawned isolate is ready for communication.
    onInitialized: () => isolates.send(counter, to: "counter"),
    // Let's tell isolate handler we might end up calling any of the
    // channels in the map.
    channels: channels.values.toList()
  );
}

// Set new count and display current count.
void setCounter(int count) {
  counter = count;
  print("Counter is now $counter");
  
  // We will no longer be needing the isolate, let's dispose of it.
  isolates.kill("counter");
}

// This function happens in the isolate.
void entryPoint(HandledIsolateContext context) {
  // Calling initialize from the entry point with the context is
  // required if communication is desired. It returns a messenger which
  // allows listening and sending information to the main isolate.
  final messenger = HandledIsolate.initialize(context);

  // Triggered every time data is received from the main isolate. We can
  // now ignore incoming data as count is kept on the native side.
  messenger.listen((data) async {
    final int result = await channels['counter'].invokeMethod('getNewCount');
    messenger.send(result);
  });
}

That's it. The only real change that happened is that we supplied our Isolate Handler with a list of channels we might need to invoke a method on and we also added an invokeMethod call inside our isolate.

Limitations #

  • Isolate Handler accesses Flutter's platform thread, as such plugins doing heavy lifting will still cause the UI to be locked up. Only Dart code is executed in the isolate—the plugin must spawn its own native thread to run in.

  • Isolate Handler uses a workaround for MethodChannel support because there is no proper way of supporting them. The workaround works well for simple applications, but may become unpredictable in more complex scenarios.

  • At the moment only MethodChannel is supported, EventChannel streams are not. Support for them may not be possible to add due to the fact that Flutter sets its own custom handler to deal with them.

  • Isolate Handler uses setMockMessageHandler to intercept calls. As there can only be one mock message handler active at any given time, another may not be set within the isolate for the one of the registered channels.

  • Custom message handlers and codecs are also not supported at the time.

Bugs #

  • Race condition where if two MethodChannels belonging to the same isolate request data at the same time, results may be sent to the wrong MethodChannel.

[0.1.1+info] - 2019-09-12

  • Added warning about platform thread potentially locking up UI.

[0.1.1] - 2019-08-27

  • Changed from MethodChannel to String for channel names to future proof in the unlikely event support for EventChannels can one day be added.

[0.1.0+formatfix2] - 2019-08-26

  • Moved example folder

[0.1.0+formatfix] - 2019-08-26

  • Fixed formatting using flutter format to better follow Flutter guidelines.

[0.1.0] - 2019-08-26

  • Initial release, major feature is support for MethodChannel calls in isolates.

example/example.dart

/// Welcome to the Isolate Handler example
///
/// In this example we will take a look at how to spawn an isolate and allow it
/// to communicate with native code by adding support for MethodChannel calls.
///
/// This will be a simple, but complete project (on the Flutter side, native
/// code is out of scope for this example). We will start an isolate and send
/// it an integer, have it add one to our count and return the value.
///
/// We will also give our isolate a name to make it easy to access from
/// anywhere.

// First, let's do our imports
import 'package:flutter/services.dart';
import 'package:isolate_handler/isolate_handler.dart';

// With that out of the way, let's create a new IsolateHandler instance. This
// is what we will use to spawn isolates.
final isolates = IsolateHandler();

// Variable where we can store the current count
int counter = 0;

// Let's store our channels in a top-level Map for convenience. We will add an
// imaginary method channel here, replace `isolates.example/counter` with your
// own channel.
const Map<String, MethodChannel> channels = {
  'counter': const MethodChannel('isolates.example/counter'),
};

void main() {
  // Start the isolate at the `entryPoint` function. We will be dealing with
  // int types here, so we will restrict communication to that type. If no type
  // is given, the type will be dynamic instead.
  isolates.spawn<int>(entryPoint,
      // Here we give a name to the isolate, by which we can access is later, for
      // example when sending it data and when disposing of it.
      name: "counter",
      // onReceive is executed every time data is received from the spawned
      // isolate. We will let the setCounter function deal with any incoming
      // data.
      onReceive: setCounter,
      // Executed once when spawned isolate is ready for communication. We will
      // send the isolate a request to perform a count right away.
      onInitialized: () => isolates.send(counter, to: "counter"),
      // Let's tell isolate handler we might end up calling any of the
      // channels in the map we made previously.
      channels: channels.values.toList());
}

void setCounter(int count) {
  // Set new count and display current count.
  counter = count;

  // Show the new count.
  print("Counter is now $counter");

  // We will no longer be needing the isolate, let's dispose of it.
  isolates.kill("counter");
}

// This function happens in the isolate.
void entryPoint(HandledIsolateContext context) {
  // Calling initialize from the entry point with the context is
  // required if communication is desired. It returns a messenger which
  // allows listening and sending information to the main isolate.
  final messenger = HandledIsolate.initialize(context);

  // Triggered every time data is received from the main isolate. We can
  // now ignore incoming data as count is kept on the native side.
  messenger.listen((data) async {
    final int result = await channels['counter'].invokeMethod('getNewCount');
    messenger.send(result);
  });
}

Use this package as a library

1. Depend on it

Add this to your package's pubspec.yaml file:


dependencies:
  isolate_handler: ^0.1.1+info

2. Install it

You can install packages from the command line:

with Flutter:


$ flutter pub get

Alternatively, your editor might support flutter pub get. Check the docs for your editor to learn more.

3. Import it

Now in your Dart code, you can use:


import 'package:isolate_handler/isolate_handler.dart';
  
Popularity:
Describes how popular the package is relative to other packages. [more]
59
Health:
Code health derived from static analysis. [more]
100
Maintenance:
Reflects how tidy and up-to-date the package is. [more]
100
Overall:
Weighted score of the above. [more]
79
Learn more about scoring.

We analyzed this package on Oct 9, 2019, and provided a score, details, and suggestions below. Analysis was completed with status completed using:

  • Dart: 2.5.1
  • pana: 0.12.21
  • Flutter: 1.9.1+hotfix.4

Platforms

Detected platforms: Flutter

References Flutter, and has no conflicting libraries.

Dependencies

Package Constraint Resolved Available
Direct dependencies
Dart SDK >=2.1.0 <3.0.0
flutter 0.0.0
Transitive dependencies
collection 1.14.11 1.14.12
meta 1.1.7
sky_engine 0.0.99
typed_data 1.1.6
vector_math 2.0.8
Dev dependencies
flutter_test