async_event_loader 0.0.2
async_event_loader: ^0.0.2 copied to clipboard
A Dart package for managing and processing asynchronous events sequentially with support for retry logic, error handling, timeout management, and real-time status tracking.
import 'dart:async';
import 'package:async_event_loader/async_event_loader.dart';
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Async Event Loader Example',
theme: ThemeData(
colorScheme: ColorScheme.fromSeed(seedColor: Colors.blue),
useMaterial3: true,
),
home: const EventLoaderPage(),
);
}
}
class EventLoaderPage extends StatefulWidget {
const EventLoaderPage({super.key});
@override
State<EventLoaderPage> createState() => _EventLoaderPageState();
}
class _EventLoaderPageState extends State<EventLoaderPage> {
late AsyncEventLoaderController _controller;
StreamSubscription<EventStatus>? _subscription;
EventStatus? _currentStatus;
final List<EventState> _eventStates = List.generate(
5,
(_) => EventState.pending,
);
@override
void initState() {
super.initState();
_initializeController();
}
void _initializeController() {
final events = [
AsyncEvent(
order: 1,
label: 'Initialize System',
action: () async {
await Future<void>.delayed(const Duration(milliseconds: 800));
},
onSuccess: () {
setState(() {
_eventStates[0] = EventState.success;
});
},
),
AsyncEvent(
order: 2,
label: 'Load Configuration',
action: () async {
await Future<void>.delayed(const Duration(milliseconds: 1000));
},
onSuccess: () {
setState(() {
_eventStates[1] = EventState.success;
});
},
),
AsyncEvent(
order: 3,
label: 'Fetch Data',
action: () async {
await Future<void>.delayed(const Duration(milliseconds: 1200));
},
onSuccess: () {
setState(() {
_eventStates[2] = EventState.success;
});
},
),
AsyncEvent(
order: 4,
label: 'Process Data',
action: () async {
await Future<void>.delayed(const Duration(milliseconds: 900));
},
onSuccess: () {
setState(() {
_eventStates[3] = EventState.success;
});
},
),
AsyncEvent(
order: 5,
label: 'Finalize',
action: () async {
await Future<void>.delayed(const Duration(milliseconds: 700));
},
onSuccess: () {
setState(() {
_eventStates[4] = EventState.success;
});
},
),
];
_controller = AsyncEventLoaderController(
events: events,
retryLimit: 3,
skipOnError: false,
);
_subscription = _controller.eventStatusStream.listen((status) {
setState(() {
_currentStatus = status;
// Mark all completed events as success
for (int i = 0; i < status.completed && i < 5; i++) {
_eventStates[i] = EventState.success;
}
// Update current event state
if (status.status.isCompleted) {
// All events completed successfully
for (int i = 0; i < 5; i++) {
_eventStates[i] = EventState.success;
}
} else if (status.status.isError) {
// Mark current event as failed
final currentIndex = status.completed;
if (currentIndex >= 0 && currentIndex < 5) {
_eventStates[currentIndex] = EventState.failure;
}
} else {
// Update current processing event
final currentIndex = status.completed;
if (currentIndex >= 0 && currentIndex < 5) {
if (status.status.isProcessing || status.status.isRetrying) {
_eventStates[currentIndex] = EventState.processing;
}
}
}
});
});
}
void _startProcessing() {
_controller.run();
}
void _reset() {
_controller.reset();
setState(() {
_eventStates.fillRange(0, 5, EventState.pending);
_currentStatus = null;
});
}
@override
void dispose() {
_subscription?.cancel();
_controller.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
backgroundColor: Theme.of(context).colorScheme.inversePrimary,
title: const Text('Async Event Loader'),
),
body: Padding(
padding: const EdgeInsets.all(24.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.stretch,
children: [
// Progress Line with 5 Points
_buildProgressLine(),
const SizedBox(height: 32),
// Current Status Card
_buildStatusCard(),
const SizedBox(height: 24),
// Event Details
_buildEventDetails(),
const Spacer(),
// Action Buttons
_buildActionButtons(),
],
),
),
);
}
Widget _buildProgressLine() {
return Column(
children: [
// Progress Line
Stack(
children: [
// Background Line
Container(
height: 4,
decoration: BoxDecoration(
color: Colors.grey[300],
borderRadius: BorderRadius.circular(2),
),
),
// Progress Fill
if (_currentStatus != null)
FractionallySizedBox(
widthFactor: _currentStatus!.progressPercentage / 100,
child: Container(
height: 4,
decoration: BoxDecoration(
color: Colors.blue,
borderRadius: BorderRadius.circular(2),
),
),
),
],
),
const SizedBox(height: 16),
// Event Points
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: List.generate(5, (index) {
return _buildEventPoint(index);
}),
),
],
);
}
Widget _buildEventPoint(int index) {
final state = _eventStates[index];
final isCurrent =
_currentStatus != null &&
_currentStatus!.completed == index &&
(_currentStatus!.status.isProcessing ||
_currentStatus!.status.isRetrying);
Color pointColor;
IconData? icon;
double size = 24;
switch (state) {
case EventState.pending:
pointColor = Colors.grey[400]!;
break;
case EventState.processing:
pointColor = Colors.blue;
icon = Icons.refresh;
break;
case EventState.success:
pointColor = Colors.green;
icon = Icons.check;
break;
case EventState.failure:
pointColor = Colors.red;
icon = Icons.close;
break;
}
return Column(
children: [
Container(
width: size,
height: size,
decoration: BoxDecoration(
color: pointColor,
shape: BoxShape.circle,
border: isCurrent ? Border.all(color: Colors.blue, width: 3) : null,
boxShadow: isCurrent
? [
BoxShadow(
color: Colors.blue.withValues(alpha: 0.3),
blurRadius: 8,
spreadRadius: 2,
),
]
: null,
),
child: icon != null
? Icon(icon, color: Colors.white, size: 16)
: null,
),
const SizedBox(height: 8),
Text(
'Event ${index + 1}',
style: TextStyle(
fontSize: 12,
color: Colors.grey[700],
fontWeight: isCurrent ? FontWeight.bold : FontWeight.normal,
),
),
],
);
}
Widget _buildStatusCard() {
if (_currentStatus == null) {
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Status: Ready',
style: Theme.of(context).textTheme.titleMedium,
),
const SizedBox(height: 8),
Text(
'Press "Start" to begin processing events',
style: Theme.of(context).textTheme.bodyMedium,
),
],
),
),
);
}
final status = _currentStatus!;
final statusText = status.status.name.toUpperCase();
final statusColor = _getStatusColor(status.status);
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
children: [
Container(
width: 12,
height: 12,
decoration: BoxDecoration(
color: statusColor,
shape: BoxShape.circle,
),
),
const SizedBox(width: 8),
Text(
'Status: $statusText',
style: Theme.of(context).textTheme.titleMedium?.copyWith(
fontWeight: FontWeight.bold,
),
),
],
),
const SizedBox(height: 12),
if (status.current.label != null)
Text(
'Current Event: ${status.current.label}',
style: Theme.of(context).textTheme.bodyLarge,
),
const SizedBox(height: 8),
Text(
'Progress: ${status.progressPercentage.toStringAsFixed(1)}%',
style: Theme.of(context).textTheme.bodyMedium,
),
const SizedBox(height: 8),
Text(
'Completed: ${status.completed} / ${status.total}',
style: Theme.of(context).textTheme.bodyMedium,
),
if (status.retryCount > 0) ...[
const SizedBox(height: 8),
Text(
'Retry Count: ${status.retryCount}',
style: Theme.of(
context,
).textTheme.bodyMedium?.copyWith(color: Colors.orange),
),
],
],
),
),
);
}
Widget _buildEventDetails() {
if (_currentStatus == null) {
return const SizedBox.shrink();
}
return Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
'Event Details',
style: Theme.of(
context,
).textTheme.titleMedium?.copyWith(fontWeight: FontWeight.bold),
),
const SizedBox(height: 12),
...List.generate(5, (index) {
final state = _eventStates[index];
final isCurrent =
_currentStatus!.completed == index &&
(_currentStatus!.status.isProcessing ||
_currentStatus!.status.isRetrying);
return Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Row(
children: [
SizedBox(
width: 100,
child: Text(
'Event ${index + 1}:',
style: Theme.of(context).textTheme.bodyMedium,
),
),
Expanded(
child: Row(
children: [
_buildStateIndicator(state),
const SizedBox(width: 8),
Text(
_getStateText(state),
style: Theme.of(context).textTheme.bodyMedium
?.copyWith(
color: _getStateColor(state),
fontWeight: isCurrent
? FontWeight.bold
: FontWeight.normal,
),
),
if (isCurrent) ...[
const SizedBox(width: 8),
const SizedBox(
width: 16,
height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
),
],
],
),
),
],
),
);
}),
],
),
),
);
}
Widget _buildStateIndicator(EventState state) {
Color color;
IconData icon;
switch (state) {
case EventState.pending:
color = Colors.grey;
icon = Icons.circle_outlined;
break;
case EventState.processing:
color = Colors.blue;
icon = Icons.refresh;
break;
case EventState.success:
color = Colors.green;
icon = Icons.check_circle;
break;
case EventState.failure:
color = Colors.red;
icon = Icons.error;
break;
}
return Icon(icon, color: color, size: 20);
}
String _getStateText(EventState state) {
switch (state) {
case EventState.pending:
return 'Pending';
case EventState.processing:
return 'Processing...';
case EventState.success:
return 'Success';
case EventState.failure:
return 'Failed';
}
}
Color _getStateColor(EventState state) {
switch (state) {
case EventState.pending:
return Colors.grey;
case EventState.processing:
return Colors.blue;
case EventState.success:
return Colors.green;
case EventState.failure:
return Colors.red;
}
}
Color _getStatusColor(AsyncStatus status) {
if (status.isCompleted) return Colors.green;
if (status.isError) return Colors.red;
if (status.isProcessing || status.isRetrying) return Colors.blue;
if (status.isPaused) return Colors.orange;
return Colors.grey;
}
Widget _buildActionButtons() {
final canStart =
_currentStatus == null ||
_currentStatus!.status.isCompleted ||
_currentStatus!.status.isError;
final canPauseResume =
_currentStatus != null &&
!_currentStatus!.status.isCompleted &&
!_currentStatus!.status.isError;
final isPaused = _currentStatus != null && _currentStatus!.status.isPaused;
final isRunning =
_currentStatus != null &&
(_currentStatus!.status.isProcessing ||
_currentStatus!.status.isRetrying);
return Row(
children: [
Expanded(
child: ElevatedButton.icon(
onPressed: canStart ? _startProcessing : null,
icon: const Icon(Icons.play_arrow),
label: const Text('Start'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
const SizedBox(width: 12),
Expanded(
child: ElevatedButton.icon(
onPressed: canPauseResume
? () {
if (isPaused) {
_controller.resume();
} else if (isRunning) {
_controller.pause();
}
setState(() {});
}
: null,
icon: Icon(isPaused ? Icons.play_arrow : Icons.pause),
label: Text(isPaused ? 'Resume' : 'Pause'),
style: ElevatedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
const SizedBox(width: 12),
Expanded(
child: OutlinedButton.icon(
onPressed: _currentStatus != null ? _reset : null,
icon: const Icon(Icons.refresh),
label: const Text('Reset'),
style: OutlinedButton.styleFrom(
padding: const EdgeInsets.symmetric(vertical: 16),
),
),
),
],
);
}
}
enum EventState { pending, processing, success, failure }