async_event_loader 0.0.2 copy "async_event_loader: ^0.0.2" to clipboard
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.

example/lib/main.dart

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 }
3
likes
150
points
106
downloads

Publisher

verified publishernahitfidanci.com

Weekly Downloads

A Dart package for managing and processing asynchronous events sequentially with support for retry logic, error handling, timeout management, and real-time status tracking.

Repository (GitHub)
View/report issues

Documentation

API reference

License

MIT (license)

Dependencies

async, equatable, rxdart

More

Packages that depend on async_event_loader