leulit_flutter_actionmanager 5.0.0 copy "leulit_flutter_actionmanager: ^5.0.0" to clipboard
leulit_flutter_actionmanager: ^5.0.0 copied to clipboard

A lightweight, type-safe action dispatcher for Flutter applications. Works seamlessly across all layers - UI, domain, data, and services.

example/lib/main.dart

import 'dart:async';
import 'package:flutter/material.dart';
import 'package:leulit_flutter_actionmanager/leulit_flutter_actionmanager.dart';

void main() {
  // Configurar logger (opcional)
  ActionManager.configureLogger(
    enabled: true,
    showTimestamp: true,
  );

  runApp(const MyApp());
}

/// Enum de acciones de ejemplo
enum AppAction {
  increment,
  decrement,
  reset,
  randomNumber,
  loadData, // Acción para demostrar loading
  asyncDataUpdate, // Nueva acción para demostrar dispatchAsync
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Action Dispatcher Demo',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  int _counter = 0;
  bool _isLoading = false;
  final _subscriptions = <StreamSubscription>[];

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

    // ✅ Pattern correcto: usar listen() con cleanup
    _subscriptions.add(
      ActionManager.listen(AppAction.increment, (event) {
        if (mounted) setState(() => _counter++);
      }),
    );

    _subscriptions.add(
      ActionManager.listen(AppAction.decrement, (event) {
        if (mounted) setState(() => _counter--);
      }),
    );

    _subscriptions.add(
      ActionManager.listen(AppAction.reset, (event) {
        if (mounted) setState(() => _counter = 0);
      }),
    );

    _subscriptions.add(
      ActionManager.listen<int>(AppAction.randomNumber, (event) {
        final number = event.data;
        if (mounted && number != null) {
          setState(() => _counter = number);
        }
      }),
    );

    // Handler para simular carga de datos
    _subscriptions.add(
      ActionManager.listen(AppAction.loadData, (event) {
        // Simular operación costosa
        Future.delayed(const Duration(seconds: 2), () {
          if (mounted) setState(() => _counter = 42);
        });
      }),
    );

    // Múltiples handlers para asyncDataUpdate
    // Handler 1: Actualización UI rápida
    _subscriptions.add(
      ActionManager.listen<int>(AppAction.asyncDataUpdate, (event) {
        final value = event.data;
        if (mounted && value != null) {
          setState(() => _counter = value);
        }
      }),
    );

    // Handler 2: Operación async (simula guardado en DB)
    _subscriptions.add(
      ActionManager.listen<int>(AppAction.asyncDataUpdate, (event) async {
        final value = event.data;
        await Future.delayed(const Duration(milliseconds: 500));
        debugPrint('✅ Datos guardados en DB: $value');
      }),
    );

    // Handler 3: Otra operación async (simula envío de analytics)
    _subscriptions.add(
      ActionManager.listen<int>(AppAction.asyncDataUpdate, (event) async {
        final value = event.data;
        await Future.delayed(const Duration(milliseconds: 300));
        debugPrint('✅ Analytics enviado: $value');
      }),
    );
  }

  @override
  void dispose() {
    // ✅ CRÍTICO: Cancelar todas las subscriptions para prevenir memory leaks
    for (final subscription in _subscriptions) {
      subscription.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Action Dispatcher Demo'),
        actions: [
          IconButton(
            icon: const Icon(Icons.info_outline),
            onPressed: () {
              // Mostrar estadísticas
              ActionManager.printSummary();

              // También en UI
              showDialog(
                context: context,
                builder: (context) => const StatsDialog(),
              );
            },
          ),
        ],
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Counter Value:', style: TextStyle(fontSize: 20)),
            Text(
              '$_counter',
              style: Theme.of(context).textTheme.displayLarge,
            ),
            const SizedBox(height: 40),
            Row(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                ElevatedButton.icon(
                  icon: const Icon(Icons.remove),
                  label: const Text('Decrement'),
                  onPressed: () {
                    ActionManager.dispatch(AppAction.decrement);
                  },
                ),
                const SizedBox(width: 10),
                ElevatedButton.icon(
                  icon: const Icon(Icons.add),
                  label: const Text('Increment'),
                  onPressed: () {
                    ActionManager.dispatch(AppAction.increment);
                  },
                ),
              ],
            ),
            const SizedBox(height: 20),
            ElevatedButton.icon(
              icon: const Icon(Icons.refresh),
              label: const Text('Reset'),
              onPressed: () {
                ActionManager.dispatch(AppAction.reset);
              },
            ),
            const SizedBox(height: 20),
            ElevatedButton.icon(
              icon: const Icon(Icons.shuffle),
              label: const Text('Random Number'),
              onPressed: () {
                final random = DateTime.now().millisecond;
                ActionManager.dispatch(AppAction.randomNumber, data: random);
              },
            ),
            const SizedBox(height: 20),
            // 🚀 Ejemplo de dispatchAsync con onComplete
            ElevatedButton.icon(
              icon: _isLoading
                  ? const SizedBox(
                      width: 20,
                      height: 20,
                      child: CircularProgressIndicator(
                        strokeWidth: 2,
                        color: Colors.white,
                      ),
                    )
                  : const Icon(Icons.cloud_upload),
              label: Text(_isLoading ? 'Procesando...' : 'Async Update'),
              onPressed: _isLoading
                  ? null
                  : () async {
                      setState(() => _isLoading = true);

                      final newValue = DateTime.now().second;
                      final messenger = ScaffoldMessenger.of(context);

                      // Usar dispatchAsync para esperar a que TODOS los listeners terminen
                      await ActionManager.dispatchAsync(
                        AppAction.asyncDataUpdate,
                        data: newValue,
                        onComplete: () {
                          debugPrint(
                              '✅ TODOS los listeners completaron (UI, DB, Analytics)');
                        },
                      );

                      // Esta línea se ejecuta DESPUÉS de que todos los listeners terminen
                      if (mounted) {
                        setState(() => _isLoading = false);
                        messenger.showSnackBar(
                          const SnackBar(
                            content: Text(
                                '✅ Actualización completa (UI + DB + Analytics)'),
                            duration: Duration(seconds: 2),
                          ),
                        );
                      }
                    },
            ),
            const SizedBox(height: 40),
            const SecondWidget(),
          ],
        ),
      ),
    );
  }
}

/// Widget adicional que también escucha las mismas acciones
class SecondWidget extends StatefulWidget {
  const SecondWidget({super.key});

  @override
  State<SecondWidget> createState() => _SecondWidgetState();
}

class _SecondWidgetState extends State<SecondWidget> {
  int _localCounter = 0;
  final _subscriptions = <StreamSubscription>[];

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

    // Este widget también responde a las acciones
    _subscriptions.add(
      ActionManager.listen(AppAction.increment, (_) {
        if (mounted) setState(() => _localCounter++);
      }),
    );

    _subscriptions.add(
      ActionManager.listen(AppAction.reset, (_) {
        if (mounted) setState(() => _localCounter = 0);
      }),
    );
  }

  @override
  void dispose() {
    for (final subscription in _subscriptions) {
      subscription.cancel();
    }
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      margin: const EdgeInsets.all(16),
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            const Text(
              'This widget also listens to actions!',
              style: TextStyle(fontWeight: FontWeight.bold),
            ),
            const SizedBox(height: 8),
            Text('Local counter: $_localCounter'),
          ],
        ),
      ),
    );
  }
}

/// Dialog que muestra estadísticas
class StatsDialog extends StatelessWidget {
  const StatsDialog({super.key});

  @override
  Widget build(BuildContext context) {
    final stats = ActionManager.getStats();

    return AlertDialog(
      title: const Text('📊 Dispatcher Stats'),
      content: Column(
        mainAxisSize: MainAxisSize.min,
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Text('Total Actions: ${stats.totalActions}'),
          Text('Total Handlers: ${stats.totalHandlers}'),
          Text('Total Dispatches: ${stats.totalDispatches}'),
          const SizedBox(height: 16),
          const Text('Action Details:',
              style: TextStyle(fontWeight: FontWeight.bold)),
          ...AppAction.values.map((action) {
            final metadata = ActionManager.getMetadata(action);
            return Padding(
              padding: const EdgeInsets.only(top: 8),
              child: Text(
                '${action.name}: ${metadata.handlerCount} handlers, '
                '${metadata.dispatchCount} dispatches',
                style: const TextStyle(fontSize: 12),
              ),
            );
          }),
        ],
      ),
      actions: [
        TextButton(
          onPressed: () => Navigator.pop(context),
          child: const Text('Close'),
        ),
      ],
    );
  }
}
0
likes
0
points
702
downloads

Publisher

unverified uploader

Weekly Downloads

A lightweight, type-safe action dispatcher for Flutter applications. Works seamlessly across all layers - UI, domain, data, and services.

Repository (GitHub)
View/report issues

Topics

#state-management #architecture #events #actions #dispatcher

License

unknown (license)

Dependencies

flutter, meta

More

Packages that depend on leulit_flutter_actionmanager