Skip to content

dotintent/flutter_isolate

Repository files navigation

FlutterIsolate

A Dart isolate is roughly equivalent to a single, independent execution thread. In a Flutter context, creating ("spawning") an isolate allows code to execute outside the main thread, which is important for running expensive/long-running tasks that would otherwise block the UI.

However, code in a spawned isolate will generally not be able to interact with Flutter plugins. This is due to the tight integration between the platform plugin scaffolding and the main application isolate.

The FlutterIsolate plugin fixes this with the introduction of a FlutterIsolate class, which is a wrapper around the platform APIs for creating isolates and setting up the bindings necessary for code running in spawned isolates to communicate with Flutter plugins.

FlutterIsolate API

AndroidiOSDescription
FlutterIsolate.spawn(entryPoint,message)spawns a new FlutterIsolate
FlutterIsolate.pause()pauses a running isolate
FlutterIsolate.resume()resumed a paused isolate
FlutterIsolate.kill()kills a an isolate
FlutterIsolate.killAll()kills all currently running isolates
FlutterIsolate.runningIsolatesreturns the IDs associated with all currently running isolates
flutterCompute(callback,message)spawns a new FlutterIsolate, runs callback and returns the returned value

Usage

To spawn a FlutterIsolate, call the spawn method with a top-level or static function that has been annotated with the @pragma('vm:entry-point') decorator:

import'package:flutter_isolate/flutter_isolate.dart'; @pragma('vm:entry-point') voidsomeFunction(String arg){print("Running in an isolate with argument : $arg")} ... classSomeWidget(){... @overrideWidgetbuild(BuildContext context){returnElevatedButton( child:Text('Run'), onPressed: (){FlutterIsolate.spawn(someFunction, "hello world")}, )}

If you just want to spawn an isolate to perform a single task (like the Flutter compute method), call flutterCompute:

@pragma('vm:entry-point') Future<int> expensiveWork(int arg) async{int result; // lots of calculationsreturn result} Future<int> doExpensiveWorkInBackground() async{returnawaitflutterCompute(expensiveWork, arg)}

Isolates can also be spawned from other isolates:

import'package:flutter_startup/flutter_startup.dart'; import'package:flutter_isolate/flutter_isolate.dart'; @pragma('vm:entry-point') voidisolate2(String arg){FlutterStartup.startupReason.then((reason){print("Isolate2 $reason")}); Timer.periodic(Duration(seconds:1),(timer)=>print("Timer Running From Isolate 2"))} @pragma('vm:entry-point') voidisolate1(String arg) async{final isolate =awaitFlutterIsolate.spawn(isolate2, "hello2"); FlutterStartup.startupReason.then((reason){print("Isolate1 $reason")}); Timer.periodic(Duration(seconds:1),(timer)=>print("Timer Running From Isolate 1"))} voidmain() async{WidgetsFlutterBinding.ensureInitialized(); final isolate =awaitFlutterIsolate.spawn(isolate1, "hello"); Timer(Duration(seconds:5), (){print("Pausing Isolate 1");isolate.pause()}); Timer(Duration(seconds:10),(){print("Resuming Isolate 1");isolate.resume()}); Timer(Duration(seconds:20),(){print("Killing Isolate 1");isolate.kill()}); runApp(MyApp())} ...

See example/lib/main.dart for example usage with the flutter_downloader plugin.

It is important to note that the entrypoint must be a top-level function, decorated with the `@pragma('vm:entry-point') annotation:

@pragma('vm:entry-point') voidtopLevelFunction(Map<String, dynamic> args){// performs work in an isolate } classMyAppextendsStatefulWidget{@override_MyAppStatecreateState() =>_MyAppState()} class_MyAppStateextendsState<MyApp>{@overridevoidinitState(){FlutterIsolate.spawn(topLevelFunction,{}); super.initState()} Widgetbuild(BuildContext context){returnContainer()} }

or a static method:

classMyAppextendsStatefulWidget{@override_MyAppStatecreateState() =>_MyAppState()} class_MyAppStateextendsState<MyApp>{@pragma('vm:entry-point') staticvoidtopLevelFunction(Map<String, dynamic> args){// performs work in an isolate } @overridevoidinitState(){FlutterIsolate.spawn(_MyAppState.staticMethod,{}); super.initState()} Widgetbuild(BuildContext context){returnContainer()} }

A class-level method will not work and will throw an Exception:

classMyAppextendsStatefulWidget{@override_MyAppStatecreateState() =>_MyAppState()} class_MyAppStateextendsState<MyApp>{voidclassMethod(Map<String, dynamic> args){// don't do this! } @overridevoidinitState(){FlutterIsolate.spawn(classMethod,{}); // this will throw NoSuchMethodError: The method 'toRawHandle' was called on null.super.initState()} Widgetbuild(BuildContext context){returnContainer()} }

Failure to add the @pragma('vm:entry-point') annotation will cause the app to crash in release mode.

Notes

Due to a FlutterIsolate being backed by a platform specific 'view', the event loop will not terminate when there is no more 'user' work left to do and FlutterIsolates will require explict termination with kill().

Additionally this plugin has not been tested with a large range of plugins, only a small subset I have been using such as flutter_notification, flutter_blue and flutter_startup.

Communicating between isolates

To pass data between isolates, a ReceivePort should be created on your (parent) isolate with the corresponding SendPort sent via the spawn method:

@pragma('vm:entry-point') voidspawnIsolate(SendPort port){port.send("Hello!")} voidmain(){var port =ReceivePort(); port.listen((msg){print("Received message from isolate $msg")}); var isolate =awaitFlutterIsolate.spawn(spawnIsolate, port.sendPort)}

Only primitives can be sent via a SendPort - see the SendPort documentation for further details.

Custom plugin registrant

See the example project for a sample implementation using a custom plugin registrant.

iOS

By default, flutter_isolate will register all plugins provided by Flutter's automatically generated GeneratedPluginRegistrant.m file.

If you want to register, e.g. some custom FlutterMethodChannels, you can define a custom registrant:

// Defines a custom plugin registrant, to be used specifically together with FlutterIsolatePlugin @objc(IsolatePluginRegistrant)classIsolatePluginRegistrant:NSObject{@objcstaticfunc register(withRegistry registry:FlutterPluginRegistry){ // Register channels for Flutter Isolate registerMethodChannelABC(bm: registry.registrar(forPlugin:"net.myapp.myChannelABC").messenger()) // Register default plugins GeneratedPluginRegistrant.register(with: registry)}} // In AppDelegate.swift @UIApplicationMain@objcclassAppDelegate:FlutterAppDelegate{overridefunc application( _ application:UIApplication, didFinishLaunchingWithOptions launchOptions:[UIApplication.LaunchOptionsKey:Any]?)->Bool{letcontroller= window.rootViewController as!FlutterViewController // Register custom channels for Flutter registerMethodChannelABC(bm: controller.binaryMessenger) // <-- the custom method channel // Point FlutterIsolatePlugin to use our previously defined custom registrant. // The string content must be equal to the plugin registrant class annotation // value: @objc(IsolatePluginRegistrant) FlutterIsolatePlugin.isolatePluginRegistrantClassName ="IsolatePluginRegistrant" // <-- GeneratedPluginRegistrant.register(with:self)return super.application(application, didFinishLaunchingWithOptions: launchOptions)}}

Android

Define a custom plugin registrant, to be used specifically together with FlutterIsolatePlugin:

publicfinalclassCustomPluginRegistrant{publicstaticvoidregisterWith(@NonNullFlutterEngineflutterEngine){flutterEngine.getPlugins().add(... [yourplugingoeshere])} }

Create a MainApplication class that sets this custom isolate registrant:

publicclassMainApplicationextendsFlutterApplication{publicMainApplication(){FlutterIsolatePlugin.setCustomIsolateRegistrant(CustomPluginRegistrant.class)} }

About

Launch an isolate that can use flutter plugins.

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages

  • Dart41.1%
  • Java30.0%
  • Objective-C19.3%
  • Ruby6.4%
  • Swift3.2%