pl_isolate 1.3.3
pl_isolate: ^1.3.3 copied to clipboard
A library to help with isolate communication.
import 'package:flutter/material.dart';
import 'dart:async';
import 'package:pl_isolate/pl_isolate.dart';
// Example operation 1: Count operation
class CountableIsolateOperation extends IsolateOperation {
@override
String get tag => 'count';
@override
Future<dynamic> run(dynamic args) async {
if (args is int) {
int countable = 0;
for (var i = 0; i < args; i++) {
countable++;
}
return countable;
}
return 0;
}
}
// Example operation 2: Calculate sum
class SumIsolateOperation extends IsolateOperation {
@override
String get tag => 'sum';
@override
Future<dynamic> run(dynamic args) async {
if (args is List && args.isNotEmpty) {
int sum = 0;
for (var num in args) {
if (num is int) {
sum += num;
}
}
return sum;
}
return 0;
}
}
// Example operation 3: Simulate async work with delay
class DelayIsolateOperation extends IsolateOperation {
@override
String get tag => 'delay';
@override
Future<dynamic> 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';
}
}
// Example operation 4: Error handling
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');
}
}
// Isolate Helper implementations - mỗi helper cho một operation riêng
// Helper cho Count operation
class CountIsolateHelper extends IsolateHelper<dynamic> {
@override
bool get isDartIsolate => false;
@override
String get name => 'CountIsolateHelper';
@override
bool get autoDispose => true;
@override
bool get isAutoDispose => true;
}
// Helper cho Sum operation
class SumIsolateHelper extends IsolateHelper<dynamic> {
@override
bool get isDartIsolate => false;
@override
String get name => 'SumIsolateHelper';
@override
bool get autoDispose => true;
@override
bool get isAutoDispose => true;
}
// Helper cho Delay operation
class DelayIsolateHelper extends IsolateHelper<dynamic> {
@override
bool get isDartIsolate => false;
@override
String get name => 'DelayIsolateHelper';
@override
bool get autoDispose => true;
@override
bool get isAutoDispose => true;
}
// Helper cho Error operation
class ErrorIsolateHelper extends IsolateHelper<dynamic> {
@override
bool get isDartIsolate => false;
@override
String get name => 'ErrorIsolateHelper';
@override
bool get autoDispose => true;
@override
bool get isAutoDispose => true;
}
void main() {
runApp(const MyApp());
}
class MyApp extends StatefulWidget {
const MyApp({super.key});
@override
State<MyApp> createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
// Mỗi helper riêng cho từng operation
final CountIsolateHelper _countHelper = CountIsolateHelper();
final SumIsolateHelper _sumHelper = SumIsolateHelper();
final DelayIsolateHelper _delayHelper = DelayIsolateHelper();
final ErrorIsolateHelper _errorHelper = ErrorIsolateHelper();
// State cho từng operation
String _countResult = 'No result yet';
bool _countLoading = false;
String? _countError;
String _sumResult = 'No result yet';
bool _sumLoading = false;
String? _sumError;
String _delayResult = 'No result yet';
bool _delayLoading = false;
String? _delayError;
String _errorResult = 'No result yet';
bool _errorLoading = false;
String? _errorError;
late final IsolateManager _isolateManager;
bool _managerRunning = false;
String _managerStatus = 'Queue idle';
String? _managerError;
final List<String> _managerLogs = [];
int _managerBatchCounter = 0;
@override
void initState() {
super.initState();
_isolateManager = IsolateManager.isInitialized
? IsolateManager.instance
: IsolateManager.init(2, 12);
_isolateManager.listenIsolateResult((IsolateResult result) {
if (!mounted) return;
final logEntry = result.errorMessage != null
? '❌ ${result.name}: ${result.errorMessage}'
: '✅ ${result.name}: ${result.result}';
setState(() {
_managerStatus = 'Last result from ${result.name}';
_managerLogs.insert(0, logEntry);
});
});
}
@override
void dispose() {
_countHelper.dispose();
_sumHelper.dispose();
_delayHelper.dispose();
_errorHelper.dispose();
_isolateManager.disposeAll();
super.dispose();
}
Future<void> _runCountOperation() async {
setState(() {
_countLoading = true;
_countError = null;
_countResult = 'Running...';
});
try {
final stopwatch = Stopwatch()..start();
final result = await _countHelper.runIsolate(
10000000,
CountableIsolateOperation(),
);
stopwatch.stop();
setState(() {
_countResult =
'Result: $result\nTime: ${stopwatch.elapsedMilliseconds}ms\nIsolate spawned: ${_countHelper.isIsolateSpawn}';
_countLoading = false;
});
} catch (e) {
setState(() {
_countError = 'Error: $e';
_countResult = 'Operation failed';
_countLoading = false;
});
}
}
Future<void> _runSumOperation() async {
setState(() {
_sumLoading = true;
_sumError = null;
_sumResult = 'Running...';
});
try {
final stopwatch = Stopwatch()..start();
final result = await _sumHelper.runIsolate(
List.generate(1000000, (i) => i),
SumIsolateOperation(),
);
stopwatch.stop();
setState(() {
_sumResult =
'Result: $result\nTime: ${stopwatch.elapsedMilliseconds}ms\nIsolate spawned: ${_sumHelper.isIsolateSpawn}';
_sumLoading = false;
});
} catch (e) {
setState(() {
_sumError = 'Error: $e';
_sumResult = 'Operation failed';
_sumLoading = false;
});
}
}
Future<void> _runDelayOperation() async {
setState(() {
_delayLoading = true;
_delayError = null;
_delayResult = 'Running...';
});
try {
final stopwatch = Stopwatch()..start();
final result = await _delayHelper.runIsolate({
'duration': 2000,
}, DelayIsolateOperation());
stopwatch.stop();
setState(() {
_delayResult =
'Result: $result\nTime: ${stopwatch.elapsedMilliseconds}ms\nIsolate spawned: ${_delayHelper.isIsolateSpawn}';
_delayLoading = false;
});
} catch (e) {
setState(() {
_delayError = 'Error: $e';
_delayResult = 'Operation failed';
_delayLoading = false;
});
}
}
Future<void> _runErrorOperation() async {
setState(() {
_errorLoading = true;
_errorError = null;
_errorResult = 'Running...';
});
try {
final stopwatch = Stopwatch()..start();
final result = await _errorHelper.runIsolate(
null,
ErrorIsolateOperation(),
);
stopwatch.stop();
setState(() {
_errorResult =
'Result: $result\nTime: ${stopwatch.elapsedMilliseconds}ms\nIsolate spawned: ${_errorHelper.isIsolateSpawn}';
_errorLoading = false;
});
} catch (e) {
setState(() {
_errorError = 'Error: $e';
_errorResult = 'Operation failed';
_errorLoading = false;
});
}
}
Future<void> _runManagerBatch() async {
if (_managerRunning) return;
setState(() {
_managerRunning = true;
_managerError = null;
_managerStatus = 'Preparing batch...';
});
final batchId = ++_managerBatchCounter;
final tasks = [
(
CountIsolateHelper(),
CountableIsolateOperation(),
3000000,
'Count to 3,000,000',
),
(
SumIsolateHelper(),
SumIsolateOperation(),
List.generate(500000, (i) => i),
'Sum 500,000 numbers',
),
(
DelayIsolateHelper(),
DelayIsolateOperation(),
{'duration': 1500},
'Delay for 1.5s',
),
(ErrorIsolateHelper(), ErrorIsolateOperation(), null, 'Simulate error'),
];
try {
for (final task in tasks) {
_isolateManager.addIsolateHelper(task.$1, task.$2, task.$3);
if (mounted) {
setState(() {
_managerLogs.insert(0, '📝 Batch $batchId scheduled: ${task.$4}');
});
} else {
_managerLogs.insert(0, '📝 Batch $batchId scheduled: ${task.$4}');
}
}
setState(() {
_managerStatus = 'Running batch $batchId with ${tasks.length} tasks';
});
await _isolateManager.runAllInBatches();
setState(() {
_managerStatus = 'Batch $batchId completed';
});
} catch (e) {
setState(() {
_managerError = 'Error running manager batch: $e';
_managerStatus = 'Batch $batchId failed';
});
} finally {
if (mounted) {
setState(() {
_managerRunning = false;
});
} else {
_managerRunning = false;
}
}
}
void _clearManagerLogs() {
setState(() {
_managerLogs.clear();
_managerStatus = 'Queue idle';
_managerError = null;
});
}
Widget _buildOperationCard(
BuildContext context, {
required String title,
required IsolateHelper helper,
required bool isLoading,
required String result,
required String? error,
required String buttonLabel,
required IconData buttonIcon,
required VoidCallback? onPressed,
required Future<void> Function() onDispose,
Color? buttonColor,
}) {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Header with title and status
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
title,
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: helper.isIsolateSpawn
? Colors.green.shade100
: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: Text(
helper.isIsolateSpawn ? 'Active' : 'Inactive',
style: TextStyle(
color: helper.isIsolateSpawn
? Colors.green.shade800
: Colors.grey.shade700,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 8),
Text(
'Helper: ${helper.name}',
style: Theme.of(
context,
).textTheme.bodySmall?.copyWith(color: Colors.grey.shade600),
),
const SizedBox(height: 16),
// Result section
Container(
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade200),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Result:',
style: Theme.of(context).textTheme.titleSmall?.copyWith(
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 8),
if (isLoading)
const Row(
children: [
SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
SizedBox(width: 8),
Text('Processing in isolate...'),
],
)
else
SelectableText(
result,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
if (error != null) ...[
const SizedBox(height: 8),
Container(
padding: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(4),
),
child: Text(
error,
style: TextStyle(
color: Colors.red.shade900,
fontSize: 12,
),
),
),
],
],
),
),
const SizedBox(height: 16),
// Action buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: onPressed,
icon: Icon(buttonIcon),
label: Text(buttonLabel),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
backgroundColor: buttonColor,
foregroundColor: buttonColor != null
? Colors.white
: null,
),
),
),
const SizedBox(width: 8),
OutlinedButton(
onPressed: isLoading ? null : onDispose,
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(
horizontal: 16,
vertical: 12,
),
),
child: const Icon(Icons.stop, size: 20),
),
],
),
],
),
),
);
}
Widget _buildManagerCard(BuildContext context) {
final logsToDisplay = _managerLogs.take(10).toList();
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(
'Isolate Manager Demo',
style: Theme.of(
context,
).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold),
),
Container(
padding: const EdgeInsets.symmetric(
horizontal: 8,
vertical: 4,
),
decoration: BoxDecoration(
color: _managerRunning
? Colors.blue.shade100
: Colors.grey.shade200,
borderRadius: BorderRadius.circular(12),
),
child: Text(
_managerRunning ? 'Running' : 'Idle',
style: TextStyle(
color: _managerRunning
? Colors.blue.shade900
: Colors.grey.shade700,
fontSize: 12,
fontWeight: FontWeight.bold,
),
),
),
],
),
const SizedBox(height: 8),
Text(
_managerStatus,
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: Colors.grey.shade700),
),
if (_managerError != null) ...[
const SizedBox(height: 8),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.red.shade50,
borderRadius: BorderRadius.circular(8),
),
child: Text(
_managerError!,
style: TextStyle(color: Colors.red.shade900, fontSize: 12),
),
),
],
const SizedBox(height: 16),
Container(
width: double.infinity,
padding: const EdgeInsets.all(12),
decoration: BoxDecoration(
color: Colors.grey.shade50,
borderRadius: BorderRadius.circular(8),
border: Border.all(color: Colors.grey.shade200),
),
child: logsToDisplay.isEmpty
? const Text(
'No manager activity yet. Press "Run manager batch" to queue demo tasks.',
style: TextStyle(fontSize: 12),
)
: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Latest results (newest first):',
style: Theme.of(context).textTheme.titleSmall
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
for (final log in logsToDisplay)
Padding(
padding: const EdgeInsets.only(bottom: 6),
child: SelectableText(
log,
style: const TextStyle(
fontFamily: 'monospace',
fontSize: 12,
),
),
),
],
),
),
const SizedBox(height: 16),
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _managerRunning ? null : _runManagerBatch,
icon: const Icon(Icons.play_arrow),
label: const Text('Run manager batch'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 12),
),
),
),
const SizedBox(width: 8),
OutlinedButton.icon(
onPressed:
(!_managerRunning &&
_managerLogs.isEmpty &&
_managerError == null)
? null
: _clearManagerLogs,
icon: const Icon(Icons.refresh),
label: const Text('Reset'),
),
],
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Isolate Helper Example',
theme: ThemeData(primarySwatch: Colors.blue, useMaterial3: true),
home: Scaffold(
appBar: AppBar(
title: const Text('Isolate Helper Example'),
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
),
body: SingleChildScrollView(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Count Operation Card
_buildOperationCard(
context,
title: 'Count Operation',
helper: _countHelper,
isLoading: _countLoading,
result: _countResult,
error: _countError,
buttonLabel: 'Count to 10,000,000',
buttonIcon: Icons.calculate,
onPressed: _countLoading ? null : _runCountOperation,
onDispose: () async {
await _countHelper.dispose();
setState(() {
_countResult = 'Isolate disposed';
_countError = null;
});
},
),
const SizedBox(height: 16),
// Sum Operation Card
_buildOperationCard(
context,
title: 'Sum Operation',
helper: _sumHelper,
isLoading: _sumLoading,
result: _sumResult,
error: _sumError,
buttonLabel: 'Sum 1,000,000 numbers',
buttonIcon: Icons.add,
onPressed: _sumLoading ? null : _runSumOperation,
onDispose: () async {
await _sumHelper.dispose();
setState(() {
_sumResult = 'Isolate disposed';
_sumError = null;
});
},
),
const SizedBox(height: 16),
// Delay Operation Card
_buildOperationCard(
context,
title: 'Delay Operation',
helper: _delayHelper,
isLoading: _delayLoading,
result: _delayResult,
error: _delayError,
buttonLabel: 'Simulate 2s delay',
buttonIcon: Icons.timer,
onPressed: _delayLoading ? null : _runDelayOperation,
onDispose: () async {
await _delayHelper.dispose();
setState(() {
_delayResult = 'Isolate disposed';
_delayError = null;
});
},
),
const SizedBox(height: 16),
// Error Operation Card
_buildOperationCard(
context,
title: 'Error Operation',
helper: _errorHelper,
isLoading: _errorLoading,
result: _errorResult,
error: _errorError,
buttonLabel: 'Test Error Handling',
buttonIcon: Icons.error_outline,
onPressed: _errorLoading ? null : _runErrorOperation,
onDispose: () async {
await _errorHelper.dispose();
setState(() {
_errorResult = 'Isolate disposed';
_errorError = null;
});
},
buttonColor: Colors.orange,
),
const SizedBox(height: 16),
_buildManagerCard(context),
const SizedBox(height: 16),
// Info Card
Card(
color: Colors.grey.shade100,
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'About',
style: Theme.of(context).textTheme.titleMedium
?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 8),
const Text(
'This example demonstrates how to use IsolateHelper to run heavy computations in a separate isolate without blocking the UI thread.',
style: TextStyle(fontSize: 12),
),
const SizedBox(height: 8),
const Text(
'• Each operation has its own IsolateHelper\n'
'• Each helper manages its own isolate\n'
'• Heavy computations run in background\n'
'• UI remains responsive\n'
'• Results are communicated back to main thread\n'
'• Isolate auto-disposes after inactivity',
style: TextStyle(fontSize: 12),
),
],
),
),
),
],
),
),
),
);
}
}