Skip to content

Events

Events let one part of your application announce that something happened without naming everything that should react to it.

Event.listen<PostPublished>(SendPostPublishedMail().call);
await event(PostPublished(postId: 42));

Defining Events

Generate an event with Sesh:

Terminal window
maat make:event PostPublished

An event is a plain Dart object carrying the data its listeners need:

import 'package:maat/maat.dart';
class PostPublished with Dispatchable {
PostPublished({required this.postId});
final int postId;
}

Dispatchable is optional. It adds an instance dispatch() method; the event() helper and Event.dispatch() work with any object.

Defining Listeners

Terminal window
maat make:listener SendPostPublishedMail --event=PostPublished
import 'package:maat/maat.dart';
import '../events/post_published.dart';
class SendPostPublishedMail extends Listener<PostPublished> {
@override
Future<void> handle(PostPublished event) async {
final post = await Post.query().findOrFail(event.postId);
final author = await post.user().get();
if (author != null) {
await Mail.to(author.email).send(PostPublishedMail(post));
}
}
}

Closures work anywhere a listener class does:

Event.listen<PostPublished>(
(event) => Log.info('post.published id=${event.postId}'),
);

Registering Listeners

Register listeners in a service provider’s boot() method, after every application binding exists:

class AppServiceProvider extends ServiceProvider {
AppServiceProvider(super.app);
@override
void boot() {
Event.listen<PostPublished>(SendPostPublishedMail().call);
Event.listen<PostPublished>(NotifyPostAuthor().call);
}
}

Listeners run sequentially in registration order, and asynchronous listeners are awaited before the next listener starts.

Dispatching Events

All three forms use the same application dispatcher:

await event(PostPublished(postId: 42));
await Event.dispatch(PostPublished(postId: 42));
await PostPublished(postId: 42).dispatch();

dispatch() returns the listener responses in order. A listener returning false stops propagation, and its false response is not added to the list.

Event.listen<PostPublished>((event) {
if (event.postId < 1) return false;
});

Event.until() stops at the first non-null response and returns it:

final allowed = await Event.until(PostPublished(postId: 42));
if (allowed == false) return;

Wildcards and Interfaces

Listener matching uses Dart’s is operator. An interface listener sees every implementation, while an Object listener sees every event:

Event.listen<Auditable>((event) => audit.record(event));
Event.listen<Object>((event) => Log.debug('event: ${event.runtimeType}'));

Subscribers

A subscriber registers several related listeners together:

class PostEventSubscriber extends EventSubscriber {
@override
void subscribe(Dispatcher events) {
events.listen<PostPublished>(_published);
events.listen<PostArchived>(_archived);
}
Future<void> _published(PostPublished event) async {}
Future<void> _archived(PostArchived event) async {}
}
Event.subscribe(PostEventSubscriber());

Inspecting and Removing Listeners

Event.hasListeners<PostPublished>();
Event.forget<PostPublished>();

hasListeners<E>() includes listeners registered for Object. forget<E>() removes listeners registered for exactly E.

Testing

Event.fake() records events instead of running their listeners. See Testing — Faking Events.

Queued listeners, automatic event discovery, and event:list are not included. Listeners run in the current isolate; queues can add deferred delivery later.