Skip to Content
Event ModuleOverview

Event Module

EventModule lets a module react to typed events tied to its own lifecycle. Register handlers when the module is entered, fire events from anywhere, and let the package cancel everything automatically when the module is disposed.

EventModule

Extend EventModule (a Module with event support) and override listen() to register handlers:

lib/src/modules/cart/cart_module.dart
class CartModule extends EventModule { @override List<ModularRoute> get routes => [ ChildRoute('/', child: (context, state) => const CartPage()), ]; @override void listen() { on<ProductAdded>((event, context) { // react to the event }); } }

listen() runs when the module initializes. The optional onAfterListen() runs right after listen() if you need a post-registration hook.

Registering handlers

on<T>( void Function(T event, BuildContext? context) callback, { bool? autoDispose, bool exclusive = false, });
  • context is the modular navigator’s current context and may be null (the navigator might not be mounted yet). Always null-check before using it.
  • autoDispose: when omitted, follows the global setting (autoDisposeEventsBus from Modular.configure, default true) — the listener is cancelled when the module is disposed. Pass false to keep it alive after disposal.
  • exclusive: see Regular vs exclusive listeners.
  • Registering on<T> twice for the same type on the same module replaces the previous handler.

There is a deprecated broadcast parameter that maps to exclusive — use exclusive instead.

Firing events

Fire an event from anywhere — a controller, a widget, another module:

ModularEvent.fire<ProductAdded>(ProductAdded(product));

ModularEvent.fire<T>(T event, {EventBus? eventBus}) dispatches on the global defaultModularEventBus unless you pass a custom bus.

Custom event bus

By default every EventModule shares the global defaultModularEventBus. Pass a custom EventBus to isolate a module’s events from the rest of the app:

class CartModule extends EventModule { CartModule() : super(eventBus: cartEventBus); // ... }

Events fired on one bus never reach listeners on another bus, even for the same event type.

Composition

To make one module also run another event module’s handlers, call the other module’s listen() synchronously inside your own listen():

class EventModuleA extends EventModule { @override void listen() { on<EventA>((event, context) { /* ... */ }); EventModuleB().listen(); // compose B's handlers into A } }

The composed (child) handlers inherit the host module’s dispose scope: they are cancelled together with the host, and re-creating the host does not duplicate them.

The composed listen() call must be synchronous inside your listen() — not inside an await or a Future callback. Otherwise the child handlers won’t attach to the host’s scope and won’t be disposed with it.

Regular vs exclusive listeners

  • Regular (exclusive: false, the default): every module that listens to a type receives the event.
  • Exclusive (exclusive: true): a FIFO queue per (bus, type) — only one listener is active and receives events at a time. When the active one is disposed, the next in the queue automatically becomes active. Useful when only the top-most / current screen should handle an event.

Global listeners

To listen without a module — with manual cleanup — use the ModularEvent singleton:

ModularEvent.instance.on<ProductAdded>((event, context) { // ... }); // later ModularEvent.instance.dispose<ProductAdded>();

ModularEvent.instance.on<T>(callback, {EventBus? eventBus, bool exclusive = false}) registers the listener and dispose<T>({EventBus? eventBus}) cancels it. These are not tied to any module lifecycle, so you must dispose them yourself.

Choosing an approach

ApproachScopeCleanup
EventModuleModule-scoped business reactionsTied to module lifecycle
ModularEventMixinWidget-scoped UI reactionsTied to widget lifecycle
ModularEvent.instanceGlobalManual

Next steps

Last updated on