# Events - [Introduction](#introduction) - [Generating Events & Listeners](#generating-events--listeners) - [Configuration](#configuration) - [Defining Events](#defining-events) - [Defining Listeners](#defining-listeners) - [Registering Events & Listeners](#registering-events--listeners) - [Dispatching Events](#dispatching-events) - [Inline Listeners](#inline-listeners) - [Framework Events](#framework-events) ## Introduction Magic provides a simple observer implementation, allowing you to subscribe and listen for various events that occur in your application. Events serve as a great way to decouple various aspects of your application, since a single event can have multiple listeners that do not depend on each other. ```dart // Dispatch an event when an order ships await Event.dispatch(OrderShipped(order)); // Listeners react to the event (send email, update analytics, etc.) ``` ## Generating Events & Listeners Use the Magic CLI to scaffold event and listener classes: ```bash dart run magic:magic make:event OrderShipped ``` This creates `lib/app/events/order_shipped.dart` with a `MagicEvent` stub. ```bash dart run magic:magic make:listener SendEmail --event=OrderShipped ``` This creates `lib/app/listeners/send_email.dart` with a `MagicListener` stub, pre-wired to the specified event. ## Configuration ### Enabling Event Support Add `EventServiceProvider` to your providers in `config/app.dart`: ```dart 'providers': [ (app) => EventServiceProvider(app), (app) => AppEventServiceProvider(app), // Your custom events // ... other providers ], ``` Create the event directories: ``` lib/app/ ├── events/ │ └── order_shipped.dart └── listeners/ └── send_shipment_notification.dart ``` ## Defining Events An event class is a simple data container holding information related to the event: ```dart import 'package:magic/magic.dart'; import '../models/order.dart'; class OrderShipped extends MagicEvent { final Order order; final String trackingNumber; OrderShipped(this.order, {required this.trackingNumber}); } ``` Events are simple data classes—they don't contain any logic. The listener is responsible for processing the event. ## Defining Listeners Event listeners receive the event instance in their `handle` method: ```dart import 'package:magic/magic.dart'; import '../events/order_shipped.dart'; class SendShipmentNotification extends MagicListener { @override Future handle(OrderShipped event) async { // Send push notification await NotificationService.send( to: event.order.user, title: 'Order Shipped!', body: 'Your order #${event.order.id} is on its way.', ); } } class UpdateAnalytics extends MagicListener { @override Future handle(OrderShipped event) async { await Analytics.track('order_shipped', { 'order_id': event.order.id, 'value': event.order.total, }); } } ``` ## Registering Events & Listeners Register mappings in your `AppEventServiceProvider`: ```dart import 'package:magic/magic.dart'; import '../events/order_shipped.dart'; import '../events/user_registered.dart'; import '../listeners/send_shipment_notification.dart'; import '../listeners/update_analytics.dart'; import '../listeners/send_welcome_email.dart'; class AppEventServiceProvider extends EventServiceProvider { AppEventServiceProvider(super.app); @override Map> get listen => { // An event can have multiple listeners OrderShipped: [ () => SendShipmentNotification(), () => UpdateAnalytics(), ], UserRegistered: [ () => SendWelcomeEmail(), ], }; } ``` ## Dispatching Events Use the `Event` facade to dispatch events: ```dart import 'package:magic/magic.dart'; import '../events/order_shipped.dart'; class OrderController extends MagicController { Future shipOrder(Order order) async { // Update order status order.status = 'shipped'; await order.save(); // Dispatch event - listeners handle the rest await Event.dispatch(OrderShipped( order, trackingNumber: generateTrackingNumber(), )); Magic.success('Shipped', 'Order has been shipped!'); } } ``` ## Inline Listeners For simple event handling, you can register listeners inline using a closure instead of creating a dedicated listener class: ```dart Event.listen((event) { Log.info('Order shipped: ${event.order.id}'); }); ``` Inline listeners are useful for quick logging, metrics, or simple side effects. For more complex logic, use dedicated listener classes. > [!TIP] > Register inline listeners in your `EventServiceProvider`'s `boot()` method to keep them organized alongside class-based listener registrations. ## Framework Events Magic fires several system events automatically. ### Authentication Events | Event | Fired When | |-------|------------| | `AuthLogin` | User successfully logs in | | `AuthLogout` | User logs out | | `AuthFailed` | Authentication attempt fails | ```dart Event.listen((event) { Log.info('User logged in: ${event.user.email}'); }); ``` ### Model Lifecycle Events | Event | Fired When | |-------|------------| | `ModelSaving` | Before model is saved | | `ModelSaved` | After model is saved | | `ModelCreating` | Before new model is created | | `ModelCreated` | After new model is created | | `ModelUpdating` | Before existing model is updated | | `ModelUpdated` | After existing model is updated | | `ModelDeleted` | After model is deleted | ```dart Event.listen((event) { if (event.model is User) { final user = event.model as User; Log.info('New user registered: ${user.email}'); } }); ``` ### Gate Events | Event | Fired When | |-------|------------| | `GateAbilityDefined` | Ability registered with `Gate.define()` | | `GateAccessChecked` | After any ability check | | `GateAccessDenied` | When access is denied | ```dart // Log denied access attempts Event.listen((event) { Log.warning('Access denied: ${event.ability} for user ${event.user?.id}'); }); ``` ### Database Events | Event | Fired When | |-------|------------| | `DatabaseConnected` | Database connection established | | `QueryExecuted` | After query execution (if enabled) | > [!TIP] > Use events to decouple your application logic. Instead of calling multiple services directly, dispatch an event and let listeners handle it independently.