isolate_runner_mixin 0.3.0
isolate_runner_mixin: ^0.3.0 copied to clipboard
A Flutter-aware Dart mixin for easily running CPU-intensive tasks, including those that use Flutter plugins, in a background isolate.
isolate_runner_mixin #
A Flutter mixin for running work off the UI isolate.
It gives you two APIs:
runInIsolate: one-off workspawnWorker+requestWorker: long-lived worker isolate
Quick start #
Use this when you are starting:
- Use
runInIsolateif you only need one background call. - Use
spawnWorker+requestWorkerif you need many calls over time.
Minimum one-off example:
import 'package:isolate_runner_mixin/isolate_runner_mixin.dart';
int _sum(int n) {
var total = 0;
for (var i = 0; i < n; i++) {
total += i;
}
return total;
}
class MyService with IsolateRunnerMixin {
Future<int> compute(int n) {
return runInIsolate(() => _sum(n));
}
}
What runInIsolate receives #
await runInIsolate(() {
// heavy code
return computeSomething();
});
You pass a callback. The callback body runs in the target isolate.
You are not passing a precomputed value.
final x = heavyCalc(); // runs on main isolate right now
await runInIsolate(() => x);
In this case, heavyCalc() already ran on the main isolate.
Rules for reliable usage #
- Keep heavy work inside the callback body.
- Prefer top-level or
staticfunctions for isolate code. - Send only supported payload/result types (
null, primitives,List,Map,TransferableTypedData,SendPort). - If you use
spawnWorker, calldisposeWorker()in your owner lifecycle.
Installation #
dependencies:
isolate_runner_mixin: <latest_version>
Run flutter pub get.
Usage: One-Off Tasks #
import 'package:isolate_runner_mixin/isolate_runner_mixin.dart';
int _sum(int n) {
var total = 0;
for (var i = 0; i < n; i++) {
total += i;
}
return total;
}
class MyService with IsolateRunnerMixin {
Future<int> heavyComputation(int n) {
return runInIsolate(
() => _sum(n),
mode: IsolateRunMode.alwaysIsolate,
timeout: const Duration(seconds: 3),
);
}
}
Modes #
IsolateRunMode.auto: background isolate only when aRootIsolateTokenis available, otherwise current isolate.IsolateRunMode.alwaysIsolate: always background isolate.IsolateRunMode.currentIsolate: always current isolate.
Preferred pattern #
// top-level or static function
bool _verify(String data, String sig, String pubKey) {
// Verification logic
return true;
}
class MyService with IsolateRunnerMixin {
final String publicKey;
MyService(this.publicKey);
Future<bool> verify(String data, String signature) {
return runInIsolate(
() => _verify(data, signature, publicKey),
mode: IsolateRunMode.alwaysIsolate,
);
}
}
Usage: Persistent Worker #
Use a persistent worker when you need many requests over time.
The handler should be top-level or static.
import 'dart:async';
FutureOr<Object?> workerHandler(String command, Object? payload) async {
switch (command) {
case 'double':
return (payload as int) * 2;
case 'delayEcho':
final map = payload as Map<Object?, Object?>;
await Future<void>.delayed(Duration(milliseconds: map['delayMs'] as int));
return map['value'];
}
throw UnsupportedError('Unknown command: $command');
}
Initialize once, then send requests:
class MyService with IsolateRunnerMixin {
Future<void> init() async {
await spawnWorker(
handler: workerHandler,
options: const SpawnWorkerOptions(maxPendingRequests: 500),
);
}
Future<int> doubleValue(int input) {
return requestWorker<int>(command: 'double', payload: input);
}
Future<void> dispose() async {
await disposeWorker();
}
}
Worker lifecycle #
spawnWorkeris idempotent.- If startup is in progress, other
spawnWorkercalls wait for the same startup. requestWorkerauto-respawns after dispose (if handler was previously registered).- Calling
spawnWorkeragain with different handler/options restarts the worker with the new configuration. disposeWorkermust be called when the owner lifecycle ends.
Payload contract #
requestWorker payloads and worker results must be sendable by this package
contract:
null,bool,num,StringList/Map(containing supported values)TransferableTypedDataSendPort
Custom classes are not accepted directly. Convert to Map/List.
Flutter Lifecycle Example #
import 'dart:async';
import 'package:flutter/widgets.dart';
import 'package:isolate_runner_mixin/isolate_runner_mixin.dart';
class _MyState extends State<MyWidget> with IsolateRunnerMixin {
@override
void initState() {
super.initState();
spawnWorker(handler: workerHandler);
}
@override
void dispose() {
unawaited(disposeWorker());
super.dispose();
}
}
In debug mode, the package prints a warning if disposeWorker is missed.
Common mistakes #
// Wrong: heavyCalc runs on main isolate before runInIsolate is called.
final value = heavyCalc();
await runInIsolate(() => value);
// Correct: heavyCalc runs in the target isolate.
await runInIsolate(() => heavyCalc());
// Wrong: custom class is not directly sendable as request payload.
await requestWorker(command: 'save', payload: MyModel(...));
// Correct: convert to map/list first.
await requestWorker(command: 'save', payload: myModel.toJson());
Example App #
For a complete app, see the example/ directory.