airo_job_scheduler 1.1.0
airo_job_scheduler: ^1.1.0 copied to clipboard
Cooperative CPU resource scheduler and isolate job executor for Flutter.
import 'dart:math';
import 'package:airo_job_scheduler/airo_job_scheduler.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const AiroJobSchedulerExampleApp());
}
class AiroJobSchedulerExampleApp extends StatelessWidget {
const AiroJobSchedulerExampleApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Airo Job Scheduler Demo',
theme: ThemeData(
useMaterial3: true,
colorSchemeSeed: Colors.indigo,
),
home: const JobSchedulerDashboardScreen(),
);
}
}
class JobSchedulerDashboardScreen extends StatefulWidget {
const JobSchedulerDashboardScreen({super.key});
@override
State<JobSchedulerDashboardScreen> createState() =>
_JobSchedulerDashboardScreenState();
}
class _JobSchedulerDashboardScreenState
extends State<JobSchedulerDashboardScreen> {
late final AiroJobSchedulerService _schedulerService;
final List<String> _logs = [];
@override
void initState() {
super.initState();
_schedulerService = AiroJobSchedulerService(
executor: const AiroWorkerExecutor(forceInline: true),
);
_schedulerService.events.listen((event) {
setState(() {
if (event is AiroJobScheduled) {
_logs.insert(0, 'π Scheduled ${event.jobId}: ${event.action.name}');
} else if (event is AiroJobCompleted) {
_logs.insert(
0, 'β
Completed ${event.jobId} in ${event.duration.inMilliseconds}ms');
} else if (event is AiroJobRetried) {
_logs.insert(
0, 'π Retrying ${event.jobId} (Attempt ${event.attempt})');
} else if (event is AiroJobFailed) {
_logs.insert(0, 'β Failed ${event.jobId}: ${event.error}');
}
});
});
}
@override
void dispose() {
_schedulerService.dispose();
super.dispose();
}
void _scheduleSampleJob() async {
final randomId = 'job-${Random().nextInt(9000) + 1000}';
try {
await _schedulerService.scheduleJob<int>(
jobId: randomId,
kind: AiroWorkerJobKind.protocolHeartbeat,
computation: () {
// Simulate CPU work
int sum = 0;
for (int i = 0; i < 100000; i++) {
sum += i;
}
return sum;
},
);
} catch (e) {
// Error handled in event listener
}
}
void _runDagWorkflow() async {
final now = DateTime.now();
final expires = now.add(const Duration(minutes: 15));
final job1 = AiroWorkerJobDescriptor(
jobId: AiroWorkerStableValue.stable('fetch-data'),
kind: AiroWorkerJobKind.deviceSync,
createdAt: now,
expiresAt: expires,
);
final job2 = AiroWorkerJobDescriptor(
jobId: AiroWorkerStableValue.stable('parse-json'),
kind: AiroWorkerJobKind.playlistImport,
createdAt: now,
expiresAt: expires,
);
final workflow = AiroJobWorkflow(
workflowId: 'sync-and-parse-wf',
jobs: [job1, job2],
);
workflow.addDependency('fetch-data', 'parse-json');
final executor = AiroJobWorkflowExecutor(
scheduler: _schedulerService,
workflow: workflow,
);
try {
await executor.executeWorkflow(
jobCallbacks: {
'fetch-data': () => 'raw_data_stream',
'parse-json': () => {'parsed': true, 'items': 42},
},
);
} catch (e) {
// Handled
}
}
@override
Widget build(BuildContext context) {
final metrics = _schedulerService.metrics;
return Scaffold(
appBar: AppBar(
title: const Text('Airo Job Scheduler Dashboard'),
actions: [
IconButton(
icon: const Icon(Icons.refresh),
onPressed: () {
setState(() {
metrics.reset();
_logs.clear();
});
},
tooltip: 'Reset Metrics',
),
],
),
body: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Metrics Summary Grid
ListenableBuilder(
listenable: metrics,
builder: (context, _) {
return GridView.count(
crossAxisCount: 4,
shrinkWrap: true,
crossAxisSpacing: 8,
mainAxisSpacing: 8,
childAspectRatio: 1.8,
children: [
_MetricCard(
title: 'Enqueued',
value: '${metrics.totalJobsEnqueued}',
color: Colors.blue,
),
_MetricCard(
title: 'Completed',
value: '${metrics.totalJobsCompleted}',
color: Colors.green,
),
_MetricCard(
title: 'Active',
value: '${metrics.currentActiveJobs}',
color: Colors.orange,
),
_MetricCard(
title: 'Avg Time',
value: '${metrics.avgExecutionTime.inMilliseconds}ms',
color: Colors.purple,
),
],
);
},
),
const SizedBox(height: 16),
// Action Buttons
Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: _scheduleSampleJob,
icon: const Icon(Icons.play_arrow),
label: const Text('Schedule Heartbeat Job'),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: _runDagWorkflow,
icon: const Icon(Icons.account_tree),
label: const Text('Run DAG Workflow'),
),
),
],
),
const SizedBox(height: 16),
// Event Logs Section
Text(
'Live Event Stream',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Expanded(
child: Card(
child: ListView.separated(
padding: const EdgeInsets.all(8),
itemCount: _logs.length,
separatorBuilder: (context, index) => const Divider(height: 1),
itemBuilder: (context, index) {
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Text(
_logs[index],
style: const TextStyle(fontFamily: 'monospace'),
),
);
},
),
),
),
],
),
),
);
}
}
class _MetricCard extends StatelessWidget {
final String title;
final String value;
final Color color;
const _MetricCard({
required this.title,
required this.value,
required this.color,
});
@override
Widget build(BuildContext context) {
return Card(
elevation: 2,
child: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
value,
style: Theme.of(context).textTheme.titleLarge?.copyWith(
color: color,
fontWeight: FontWeight.bold,
),
),
const SizedBox(height: 2),
Text(
title,
style: Theme.of(context).textTheme.labelSmall,
),
],
),
),
);
}
}