Skip to main content

🎭 Event System

The Event System is designed to enable decoupled communication between modules. This means one module can notify or trigger actions in another module without direct dependencies, making your app more modular and maintainable.

✨ Why Use Events?​

  • Decouple features: modules don't need to know about each other
  • Centralize cross-cutting actions (navigation, notifications, etc)
  • Facilitate micro frontend/team-based architectures

🚦 Typical Use Cases​

1. Auth: Redirect after login​

When a user logs in, the Auth module can fire an event to notify other modules (or the app shell) to redirect to a specific page:

// Event definition
typedef void OnLoginSuccess();

// In AuthModule
class AuthModule extends EventModule {

void listen() {
on<OnLoginSuccess>((event, context) {
context.go('/dashboard');
});
}
}

// Somewhere after successful login:
ModularEvent.fire(OnLoginSuccess());

2. Dio Exception: RefreshTokenExpired β†’ Redirect to login​

Quando uma exceΓ§Γ£o de token expirado ocorre em qualquer mΓ³dulo, um evento pode ser disparado para redirecionar o usuΓ‘rio para a tela de login, sem acoplamento entre mΓ³dulos:

// Event definition
class RefreshTokenExpired {}

// In a Dio interceptor (anywhere in the app)
try {
// ...
} on DioError catch (e) {
if (e.type == DioErrorType.badResponse && e.error == 'RefreshTokenExpired') {
ModularEvent.fire(RefreshTokenExpired());
}
}

// In AuthModule or a global event listener
ModularEvent.instance.on<RefreshTokenExpired>((event, context) {
context.go('/login');
});

πŸ”„ Event Flow Example​


(Advanced usage and patterns below)

πŸš€ Creating Event Modules​

Basic Event Module​

class NotificationModule extends EventModule {

List<ModularRoute> get routes => [
ChildRoute('/', child: (context, state) => NotificationPage()),
];


void listen() {
on<ShowNotificationEvent>((event, context) {

ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text(event.message)),
);

});
}
}

class ShowNotificationEvent {
final String message;
ShowNotificationEvent(this.message);
}

Event with Data​

class UserLoggedInEvent {
final User user;
final DateTime timestamp;

UserLoggedInEvent(this.user, this.timestamp);
}

class AuthModule extends EventModule {

void listen() {
on<UserLoggedInEvent>((event, context) {
// Handle user login
print('User ${event.user.name} logged in at ${event.timestamp}');

// Navigate to dashboard

context.go('/dashboard');

});
}
}

🌐 Global Events​

Register Global Listeners​

// Register global listener
ModularEvent.instance.on<UserLoggedOutEvent>((event, context) {
context.go('/login');
});

// Fire global events
ModularEvent.fire(UserLoggedOutEvent());

Module-Specific Events​

class CartModule extends EventModule {

void listen() {
on<ProductAddedEvent>((event, context) {
// Add to cart logic
addToCart(event.product);

// Fire another event
ModularEvent.fire(CartUpdatedEvent());
});
}
}

🎯 Context-Aware Events​

class NotificationModule extends EventModule {

void listen() {
on<ShowDialogEvent>((event, context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: Text(event.title),
content: Text(event.message),
actions: [
TextButton(
onPressed: () => Navigator.of(context).pop(),
child: Text('OK'),
),
],
),
);
});
}
}

Why Context Can Be Null​

The NavigatorContext can be null when:

  • The event is fired before any widget is built
  • The module is not currently active in the navigation stack
  • The event is fired from a background service

πŸ”„ Event Communication Patterns​

Request-Response Pattern​

class DataRequestEvent {
final String id;
final Function(String data) onResponse;

DataRequestEvent(this.id, this.onResponse);
}

class DataModule extends EventModule {

void listen() {
on<DataRequestEvent>((event, context) async {
final data = await fetchData(event.id);
event.onResponse(data);
});
}
}

// Usage
ModularEvent.fire(DataRequestEvent('user-123', (data) {
print('Received data: $data');
}));

Broadcast Pattern​

class ThemeChangedEvent {
final bool isDarkMode;
ThemeChangedEvent(this.isDarkMode);
}

class ThemeModule extends EventModule {

void listen() {
on<ThemeChangedEvent>((event, context) {
// Update theme across all modules
updateTheme(event.isDarkMode);
});
}
}

🧩 Micro Frontend Example​

E-commerce Architecture​

Cart Module (Team A)​

class CartModule extends EventModule {

void listen() {
on<ProductAddedEvent>((event, context) {
// Add to cart logic
addToCart(event.product);

// Notify other modules
ModularEvent.fire(CartUpdatedEvent());
});

on<PaymentSuccessEvent>((event, context) {
// Clear cart after successful payment
clearCart();
});
}
}

Product Module (Team B)​

class ProductModule extends EventModule {

void listen() {
on<CartUpdatedEvent>((event, context) {
// Update product availability
updateProductAvailability();
});
}
}

Payment Module (Team C)​

class PaymentModule extends EventModule {

void listen() {
on<CartUpdatedEvent>((event, context) {
// Update payment totals
updatePaymentTotals();
});
}
}

🎨 Advanced Event Patterns​

Event with Validation​

class PurchaseEvent {
final Product product;
final int quantity;
final String userId;

PurchaseEvent(this.product, this.quantity, this.userId);

bool isValid() {
return product != null && quantity > 0 && userId.isNotEmpty;
}
}

class PurchaseModule extends EventModule {

void listen() {
on<PurchaseEvent>((event, context) {
if (!event.isValid()) {
ModularEvent.fire(ErrorEvent('Invalid purchase data'));
return;
}

// Process purchase
processPurchase(event);
});
}
}

Event Chaining​

class OrderModule extends EventModule {

void listen() {
on<OrderCreatedEvent>((event, context) {
// Create order
final order = createOrder(event);

// Fire next event in chain
ModularEvent.fire(OrderProcessedEvent(order));
});

on<OrderProcessedEvent>((event, context) {
// Process order
processOrder(event.order);

// Fire final event
ModularEvent.fire(OrderCompletedEvent(event.order));
});
}
}

πŸ›‘οΈ Best Practices​

1. Event Naming​

// βœ… Good - Clear and descriptive
class UserProfileUpdatedEvent
class PaymentProcessedEvent
class CartItemRemovedEvent

// ❌ Avoid - Vague names
class UpdateEvent
class ProcessEvent
class RemoveEvent

2. Event Data​

// βœ… Good - Immutable data
class ProductAddedEvent {
final Product product;
final int quantity;

const ProductAddedEvent(this.product, this.quantity);
}

// ❌ Avoid - Mutable data
class ProductAddedEvent {
Product product;
int quantity;
}