Event Module
Decoupled communication between modules using an event-driven architecture.
Overview
EventModule extends Module with the ability to listen for and respond to events. It manages listener lifecycle automatically and provides navigation context to event handlers.
Use events when modules need to react to actions from other modules without creating direct dependencies.
Quick Start
1. Define an Event
class UserLoggedInEvent {
final String userId;
UserLoggedInEvent({required this.userId});
}2. Create an Event Module
class AuthModule extends EventModule {
@override
void listen() {
on<UserLoggedInEvent>((event, context) {
if (context != null) {
context.go('/dashboard');
}
});
}
@override
List<ModularRoute> get routes => [
ChildRoute('/login', child: (_, __) => LoginPage()),
];
}3. Fire Events
ModularEvent.fire(UserLoggedInEvent(userId: '123'));Any module listening for UserLoggedInEvent will receive it automatically.
Event Types
Regular Events
Multiple modules can listen to the same event. All listeners are called:
@override
void listen() {
on<DataUpdatedEvent>((event, context) {
print('Data: ${event.data}');
});
}Exclusive Events
Only one module receives the event at a time (FIFO queue). Use this for events that should be handled exactly once:
@override
void listen() {
on<ImportantEvent>((event, context) {
print('Handled: ${event.message}');
}, exclusive: true);
}Navigation Context
Event handlers receive a BuildContext? parameter. This context is injected automatically and can be used for navigation:
on<ShowProfileEvent>((event, context) {
if (context != null) {
context.go('/profile/${event.userId}');
}
});Always check for null before using the context.
Complete Example
// Events
class UserLoggedInEvent {
final String userId;
UserLoggedInEvent({required this.userId});
}
class ShowSnackBarEvent {
final String message;
ShowSnackBarEvent({required this.message});
}
// Auth module listens for login
class AuthModule extends EventModule {
@override
void listen() {
on<UserLoggedInEvent>((event, context) {
if (context != null) {
context.go('/dashboard');
}
});
}
@override
List<ModularRoute> get routes => [
ChildRoute('/login', child: (_, __) => LoginPage()),
];
}
// UI module listens for snackbar events
class UIModule extends EventModule {
@override
void listen() {
on<ShowSnackBarEvent>((event, context) {
if (context != null) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(event.message)),
);
}
});
}
}
// Fire events from anywhere
ModularEvent.fire(UserLoggedInEvent(userId: '123'));
ModularEvent.fire(ShowSnackBarEvent(message: 'Welcome!'));Lifecycle
| Event | What happens |
|---|---|
| Module initializes | listen() is called, listeners are registered |
| Event is fired | All registered listeners for that event type are invoked |
| Module is disposed | Listeners are automatically unregistered |
No manual cleanup is required.
When to Use
| Use case | EventModule? |
|---|---|
| Cross-module notifications (auth, analytics) | Yes |
| Navigation triggers from business logic | Yes |
| UI updates across module boundaries | Yes |
| Simple parent-child communication | No — use callbacks |
| Direct data passing between widgets | No — use DI |
| High-frequency data streams | No — use streams |
Best Practices
- Always check
context != nullbefore navigating in event handlers. - Use descriptive event names —
UserLoggedInEvent, notEvent1. - Keep events as plain data — No business logic in event classes.
- Use
exclusive: truesparingly — Only when the event must be handled exactly once. - Define events close to the producer — The module that fires the event should own the event class.
Listening in Widgets
To listen to events inside a StatefulWidget without creating a full module, use ModularEventMixin. It provides the same on<E>() API and cancels subscriptions automatically when the widget is disposed.
See ModularEventMixin for the full reference.