π 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β
NavigatorContext Usageβ
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;
}
π Related Topicsβ
- ποΈ Project Structure - Organize your modules
- π Dependency Injection - Manage dependencies
- π£οΈ Routes - Navigation between modules