search ESC

Searching…

No results for "".

Type at least 2 characters to search.

Docs

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.

// 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:

dart run magic:magic make:event OrderShipped

This creates lib/app/events/order_shipped.dart with a MagicEvent stub.

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:

'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:

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:

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:

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:

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:

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
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
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
// 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.