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

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

Leulit Flutter Action Manager #

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

pub package License: MIT

✨ Features #

  • Type-safe with Dart enums (no string typos!)
  • Zero external dependencies (only Flutter/Dart)
  • Universal: Works in widgets, controllers, services, repositories
  • Reactive: StreamSubscription support for easy cleanup
  • Debuggable: Built-in introspection and statistics
  • Memory-safe: Proper handler cleanup
  • Lightweight: < 300 lines of code

🚀 Installation #

Add to your pubspec.yaml:

dependencies:
  leulit_flutter_actionmanager: ^4.1.0

Then run:

flutter pub get

📚 Basic Usage #

1. Define your actions #

enum AppAction {
  userLoggedIn,
  userLoggedOut,
  dataLoaded,
  networkError,
  zoomIn,
  zoomOut,
}

2. Dispatch actions #

// Without data
ActionManager.dispatch(AppAction.zoomIn);

// With data
ActionManager.dispatch(AppAction.userLoggedIn, data: user);

// With additional context
ActionManager.dispatch(
  AppAction.networkError,
  data: errorMessage,
  context: {'retry': true, 'endpoint': '/api/users'},
);

3. Register handlers #

// Simple handler
ActionManager.onVoid(AppAction.zoomIn, () {
  print('Zooming in!');
});

// Handler with typed data
ActionManager.on<User>(AppAction.userLoggedIn, (user) {
  print('Welcome ${user?.name}!');
});

// Handler with full ActionEvent metadata
ActionManager.onFull<String>(AppAction.networkError, (action) {
  print('Error at ${action.timestamp}: ${action.data}');
  print('Context: ${action.context}');
});

4. Reactive Widgets (NEW in 4.1.0) #

The easiest way to build reactive UIs - widgets that automatically rebuild when actions are dispatched:

// Simple reactive widget
ActionManagerWidget<String>(
  action: AppAction.statusUpdated,
  builder: (context, status) {
    return Text('Status: ${status ?? "idle"}');
  },
)

// With placeholder before first event
ActionManagerWidget<User>(
  action: AppAction.userLoggedIn,
  placeholder: CircularProgressIndicator(),
  builder: (context, user) {
    return Text('Welcome ${user?.name ?? "Guest"}!');
  },
)

// React to multiple actions
ActionManagerMultiWidget(
  actions: [AppAction.refresh, AppAction.dataUpdated],
  builder: (context, event) {
    return Text('Last update: ${event?.timestamp}');
  },
)

Benefits:

  • ✅ Auto-subscription and cleanup (no dispose needed)
  • ✅ Automatic setState() when action is dispatched
  • ✅ Type-safe with generics
  • ✅ Optional placeholder widget

🎯 Use Cases #

In Widgets (Manual Subscription) #

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

class _MyWidgetState extends State<MyWidget> {
  StreamSubscription? _sub;
  
  @override
  void initState() {
    super.initState();
    
    // Listen and rebuild
    _sub = ActionManager.listen(AppAction.dataLoaded, (_) {
      setState(() {});
    });
  }
  
  @override
  void dispose() {
    _sub?.cancel();
    super.dispose();
  }
  
  @override
  Widget build(BuildContext context) {
    return ElevatedButton(
      onPressed: () => ActionManager.dispatch(AppAction.zoomIn),
      child: Text('Zoom +'),
    );
  }
}

In Controllers (GetX example) #

class IssueController extends GetxController {
  @override
  void onInit() {
    super.onInit();
    
    ActionManager.on<String>(AppAction.deleteIssue, (id) {
      if (id != null) _deleteIssue(id);
    });
    
    ActionManager.on<List<Issue>>(AppAction.issuesLoaded, (issues) {
      this.issues = issues ?? [];
      update(); // Trigger UI rebuild
    });
  }
  
  void deleteIssue(String id) {
    ActionManager.dispatch(AppAction.deleteIssue, data: id);
  }
}

In Services/Repositories #

class IssueRepository {
  IssueRepository() {
    // Listen to UI commands
    ActionManager.on<String>(AppAction.deleteIssue, (id) async {
      if (id != null) {
        await _api.deleteIssue(id);
        await loadIssues(); // Reload data
      }
    });
  }
  
  Future<void> loadIssues() async {
    try {
      final issues = await _api.getIssues();
      
      // Notify entire app
      ActionManager.dispatch(AppAction.issuesLoaded, data: issues);
    } catch (e) {
      ActionManager.dispatch(AppAction.networkError, data: e.toString());
    }
  }
}

🔍 Debugging & Introspection #

// Check if action has handlers
if (ActionManager.hasHandlers(AppAction.test)) {
  print('Someone is listening!');
}

// Get handler count
final count = ActionManager.handlerCount(AppAction.test);
print('Handlers: $count');

// Get detailed metadata
final metadata = ActionManager.getMetadata(AppAction.test);
print(metadata); // Shows count, dispatches, last time, etc.

// Get global statistics
final stats = ActionManager.getStats();
print(stats); // Shows all actions, handlers, dispatches

// Print visual summary
ActionManager.printSummary();

Output example:

📊 ═══════════════════════════════════════════
📊 Action Dispatcher Summary
📊 ═══════════════════════════════════════════

🎯 userLoggedIn
   ├─ Handlers: 3
   ├─ Dispatched: 15 times
   └─ Last: 2026-02-09 10:30:45.123

🎯 dataLoaded
   ├─ Handlers: 2
   ├─ Dispatched: 42 times
   └─ Last: 2026-02-09 10:32:10.456

📊 ═══════════════════════════════════════════
📊 Total: 2 actions, 5 handlers, 57 dispatches
📊 ═══════════════════════════════════════════

⚙️ Configuration #

// Configure logger
ActionManager.configureLogger(
  enabled: true,           // Enable/disable logging
  showTimestamp: true,     // Include timestamp in logs
);

// Clear all handlers (useful for tests)
ActionManager.clear();

🧪 Testing #

void main() {
  setUp(() {
    ActionManager.clear(); // Clean slate for each test
  });

  test('should handle action', () {
    bool called = false;
    
    ActionManager.onVoid(AppAction.test, () {
      called = true;
    });
    
    ActionManager.dispatch(AppAction.test);
    
    expect(called, isTrue);
  });
}

🤔 Why ActionManager? #

Before (Multiple paradigms) #

// UI: GetX reactivity
Obx(() => Text(controller.data.value))

// Cross-controller: EventBus
eventBus.fire(MyEvent(data));

// Service → UI: Callbacks
service.onData = (data) => setState(() {});

// Repository: Streams
repo.dataStream.listen((data) => ...);

After (One system) #

// Everywhere:
ActionManager.dispatch(AppAction.dataLoaded, data: data);
ActionManager.on<Data>(AppAction.dataLoaded, (data) => ...);

Benefits:

  • One pattern to learn
  • Consistent throughout the codebase
  • Easy to debug (see all actions in one place)
  • No confusion about which system to use

📄 License #

MIT License - see LICENSE file for details.

🤝 Contributing #

Contributions are welcome! Please feel free to submit a Pull Request.

📬 Support #

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