Async Event Loader

style: very good analysis License: MIT

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

Features ✨

  • ➡️ Sequential Processing: Events are processed one at a time in order
  • 🔁 Retry Logic: Automatic retry with configurable retry limit
  • 🛡️ Error Handling: Configurable error handling with skip-on-error option
  • Timeout Management: Both controller-level and event-level timeouts
  • 📊 Status Tracking: Real-time status updates via streams
  • ⏯️ Pause/Resume: Ability to pause and resume processing
  • 📈 Progress Tracking: Built-in progress percentage calculation

Installation 💻

Install via dart pub add:

dart pub add async_event_loader

Or add it to your pubspec.yaml:

dependencies:
  async_event_loader: ^0.0.2

Quick Start 🚀

Basic Usage

import 'package:async_event_loader/async_event_loader.dart';

// Create events
final events = [
  AsyncEvent(
    label: 'Load Data',
    action: () async {
      final data = await fetchData();
      return data;
    },
    onSuccess: () => print('Data loaded successfully'),
    onError: (error) => print('Error: $error'),
  ),
  AsyncEvent(
    label: 'Process Data',
    action: () async {
      await processData();
    },
    onSuccess: () => print('Data processed'),
  ),
];

// Create controller
final controller = AsyncEventLoaderController(
  events: events,
  retryLimit: 3,
  skipOnError: false,
);

// Listen to status updates
controller.eventStatusStream.listen((status) {
  print('Progress: ${status.progressPercentage}%');
  print('Status: ${status.status}');
  print('Completed: ${status.completed}/${status.total}');
});

// Start processing
controller.run();

Flutter Example

import 'package:async_event_loader/async_event_loader.dart';
import 'package:flutter/material.dart';

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

class _MyWidgetState extends State<MyWidget> {
  late AsyncEventLoaderController _controller;
  EventStatus? _status;

  @override
  void initState() {
    super.initState();
    _initializeController();
  }

  void _initializeController() {
    final events = [
      AsyncEvent(
        label: 'Initialize',
        action: () async => await initializeSystem(),
        onSuccess: () => print('Initialized'),
      ),
      AsyncEvent(
        label: 'Load Data',
        action: () async => await loadData(),
        onSuccess: () => print('Data loaded'),
      ),
    ];

    _controller = AsyncEventLoaderController(
      events: events,
      retryLimit: 3,
    );

    _controller.eventStatusStream.listen((status) {
      setState(() {
        _status = status;
      });
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        if (_status != null) ...[
          Text('Progress: ${_status!.progressPercentage.toStringAsFixed(1)}%'),
          LinearProgressIndicator(
            value: _status!.progress / 100,
          ),
          Text('Status: ${_status!.status.name}'),
        ],
        ElevatedButton(
          onPressed: () => _controller.run(),
          child: Text('Start'),
        ),
        ElevatedButton(
          onPressed: () => _controller.pause(),
          child: Text('Pause'),
        ),
        ElevatedButton(
          onPressed: () => _controller.resume(),
          child: Text('Resume'),
        ),
      ],
    );
  }

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

Advanced Usage 📚

With Timeouts

final controller = AsyncEventLoaderController(
  events: [
    AsyncEvent(
      label: 'Fetch Data',
      action: () async => await fetchData(),
      timeoutDuration: Duration(seconds: 5), // Event-level timeout
    ),
  ],
  timeoutDuration: Duration(seconds: 30), // Controller-level timeout
  retryLimit: 3,
);

With Error Handling

final controller = AsyncEventLoaderController(
  events: [
    AsyncEvent(
      label: 'Risky Operation',
      action: () async {
        if (Random().nextBool()) {
          throw Exception('Random error');
        }
        return 'Success';
      },
      onError: (error) => print('Operation failed: $error'),
    ),
  ],
  retryLimit: 3,
  skipOnError: true, // Continue to next event on error
);

Pause and Resume

// Pause processing
controller.pause();

// Resume processing
controller.resume();

// Reset to initial state
controller.reset();

Status Monitoring

controller.eventStatusStream.listen((status) {
  switch (status.status) {
    case AsyncStatus.initial:
      print('Ready to start');
      break;
    case AsyncStatus.processing:
      print('Processing: ${status.current.label}');
      break;
    case AsyncStatus.retrying:
      print('Retrying (attempt ${status.retryCount})');
      break;
    case AsyncStatus.completed:
      print('All events completed!');
      break;
    case AsyncStatus.error:
      print('Error occurred');
      break;
    case AsyncStatus.paused:
      print('Processing paused');
      break;
    default:
      break;
  }
});

API Reference 📖

AsyncEventLoaderController

The main controller for managing async events.

Constructor:

AsyncEventLoaderController({
  required List<AsyncEvent> events,
  int retryLimit = 3,
  bool skipOnError = false,
  Duration? timeoutDuration,
})

Methods:

  • run() - Starts processing events
  • pause() - Pauses processing
  • resume() - Resumes processing after pause
  • reset() - Resets controller to initial state
  • dispose() - Disposes the controller

Properties:

  • eventStatusStream - Stream of status updates
  • currentEventStatus - Current status
  • isCompleted - Whether all events are completed
  • isProcessing - Whether currently processing
  • isPaused - Whether paused
  • isError - Whether an error occurred

AsyncEvent

Represents a single async event to be processed.

Constructor:

AsyncEvent({
  required Future<dynamic> Function() action,
  void Function()? onSuccess,
  void Function()? onRetry,
  void Function(dynamic error)? onError,
  void Function()? onCancel,
  int? order,
  String? label,
  Duration? timeoutDuration,
})

EventStatus

Represents the current status of event processing.

Properties:

  • status - Current AsyncStatus
  • current - Current AsyncEvent being processed
  • total - Total number of events
  • completed - Number of completed events
  • retryCount - Current retry count
  • progress - Progress as a value between 0.0 and 1.0
  • progressPercentage - Progress as a percentage (0-100)

AsyncStatus

Enum representing the various states:

  • initial - Initial state before processing
  • processing - Currently processing an event
  • retrying - Retrying a failed event
  • paused - Processing is paused
  • completed - All events completed successfully
  • error - An error occurred
  • canceled - Processing was canceled

Continuous Integration 🤖

Async Event Loader comes with a built-in GitHub Actions workflow powered by Very Good Workflows but you can also add your preferred CI/CD solution.

Out of the box, on each pull request and push, the CI formats, lints, and tests the code. This ensures the code remains consistent and behaves correctly as you add functionality or make changes. The project uses Very Good Analysis for a strict set of analysis options used by our team. Code coverage is enforced using the Very Good Workflows.


Libraries

async_event_loader
A Dart package for managing and processing asynchronous events sequentially.