pl_isolate

https://github.com/user-attachments/assets/31985cfa-f3b4-440e-8459-fdc4f8dc7fe1

A powerful Flutter plugin that simplifies isolate communication and management.
Run heavy computations in separate isolates without blocking the UI thread.


โœจ Features

  • ๐Ÿš€ Easy isolate lifecycle management
  • ๐Ÿ”„ Auto-dispose isolates after inactivity
  • ๐ŸŽฏ Strongly-typed operations (IsolateOperation<T>)
  • ๐Ÿงฉ One helper per operation (clean architecture friendly)
  • โšก Non-blocking UI for CPU-intensive work
  • ๐Ÿ“Š Built-in IsolateManager for batching & concurrency control
  • ๐Ÿ” Safe error propagation from isolate โ†’ UI
  • ๐Ÿ“ฆ Optimized data transfer (supports large payloads)

๐Ÿ“ฆ Installation

Add this to your pubspec.yaml:

dependencies:
  pl_isolate: ^1.0.0

Then run:

flutter pub get

๐Ÿš€ Quick Start

1. Define an Operation

Each task is represented by an IsolateOperation<T>.

import 'package:pl_isolate/pl_isolate.dart';

class CountableIsolateOperation extends IsolateOperation<int> {
  @override
  String get tag => 'count';

  @override
  Future<int> run(dynamic args) async {
    if (args is int) {
      int count = 0;
      for (var i = 0; i < args; i++) {
        count++;
      }
      return count;
    }
    return 0;
  }
}

2. Create a Helper

Each operation has its own IsolateHelper.

class CountIsolateHelper
    extends IsolateHelper<dynamic, CountableIsolateOperation> {

  @override
  bool get isDartIsolate => false;

  @override
  String get name => 'CountIsolateHelper';

  @override
  bool get isAutoDispose => true;

  CountIsolateHelper(super.operation);
}

โœ… Operation is injected via constructor โ€” NOT passed at runtime


3. Execute in UI

class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late final CountIsolateHelper _helper;

  @override
  void initState() {
    super.initState();
    _helper = CountIsolateHelper(CountableIsolateOperation());
  }

  Future<void> runTask() async {
    try {
      final result = await _helper.runIsolate(1000000);
      print('Result: $result');
    } catch (e) {
      print('Error: $e');
    }
  }

  @override
  void dispose() {
    _helper.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: runTask,
      child: const Text('Run Task'),
    );
  }
}

๐Ÿง  Core Concept

IsolateOperation  โ†’  Business logic
IsolateHelper     โ†’  Isolate lifecycle + execution
IsolateManager    โ†’  Queue + concurrency control

โš™๏ธ Using IsolateManager (Advanced)

Initialize

final manager = IsolateManager.init(2, 12);
// 2 concurrent tasks, max 12 in queue

Add Tasks

manager.addIsolateHelper(
  CountIsolateHelper(CountableIsolateOperation()),
  3000000,
);

manager.addIsolateHelper(
  SumIsolateHelper(SumIsolateOperation()),
  List.generate(500000, (i) => i),
);

Listen for Results

manager.listenIsolateResult((result) {
  if (result.errorMessage != null) {
    print('โŒ ${result.name}: ${result.errorMessage}');
  } else {
    print('โœ… ${result.name}: ${result.result}');
  }
});

Run Batch

await manager.runAllInBatches();

Dispose

manager.disposeAll();

๐Ÿงช Example Operations

Sum Operation

class SumIsolateOperation extends IsolateOperation<int> {
  @override
  String get tag => 'sum';

  @override
  Future<int> run(dynamic args) async {
    if (args is List) {
      int sum = 0;
      for (final item in args) {
        if (item is int) sum += item;
      }
      return sum;
    }
    return 0;
  }
}

Delay Operation

class DelayIsolateOperation extends IsolateOperation<String> {
  @override
  String get tag => 'delay';

  @override
  Future<String> run(dynamic args) async {
    if (args is Map && args.containsKey('duration')) {
      final duration = args['duration'] as int;
      await Future.delayed(Duration(milliseconds: duration));
      return 'Completed after ${duration}ms';
    }
    return 'Invalid arguments';
  }
}

Error Operation

class ErrorIsolateOperation extends IsolateOperation {
  @override
  String get tag => 'error';

  @override
  Future<dynamic> run(dynamic args) async {
    throw Exception('This is a test error from isolate');
  }
}

๐Ÿงฉ Best Practices

1. One Helper = One Operation

// โœ… Good
CountIsolateHelper
SumIsolateHelper

// โŒ Avoid
GenericHelper

2. Do NOT pass operation into runIsolate

// โœ… Correct
helper.runIsolate(args);

// โŒ Wrong (old API)
helper.runIsolate(args, operation);

3. Always Dispose

@override
void dispose() {
  helper.dispose();
  super.dispose();
}

4. Use Manager for Heavy Workloads

  • Few tasks โ†’ use helper directly
  • Many tasks โ†’ use IsolateManager

5. Pass Serializable Data Only

// โœ… OK
int, String, List, Map

// โŒ Avoid
BuildContext, Function, Stream

โšก Dart Isolate vs UI Isolate

Regular Isolate (isDartIsolate = false)

  • Best performance
  • No UI access
  • Recommended for most cases

UI Isolate (isDartIsolate = true)

  • Needed for platform channels
  • Slightly heavier

๐Ÿงฏ Troubleshooting

Isolate not running?

  • Check helper initialization
  • Verify isDartIsolate

Serialization error?

  • Ensure data is transferable

Memory leak?

  • Forgot dispose()

โค๏ธ Why pl_isolate?

Feature compute() pl_isolate
Reuse isolate โŒ โœ…
Queue system โŒ โœ…
Error handling Basic Advanced
Typed API โŒ โœ…
Batch execution โŒ โœ…

๐Ÿค Contributing

Pull requests are welcome!


๐Ÿ“„ License

MIT License


๐Ÿ’ฌ Support

https://github.com/NexPlugs/pl_isolate/issues


Made with โค๏ธ by NexPlugs

Libraries

pl_isolate