In an app, we often need a broadcasting mechanism for cross-page event notifications. For example, in a login-required app, pages need to listen for user login or logout events to update their states accordingly. In this case, an event bus can be very useful. The event bus typically implements the subscriber pattern, which includes two roles: publisher and subscriber. Events can be triggered and listened to through the event bus. In this section, we will implement a simple global event bus using the singleton pattern, as shown in the code below:
// Subscriber callback signature typedef void EventCallback(arg); class EventBus { // Private constructor EventBus._internal(); // Store singleton instance static EventBus _singleton = EventBus._internal(); // Factory constructor factory EventBus() => _singleton; // Store event subscriber queue, key: event name (id), value: corresponding subscriber queue final _emap = Map<Object, List<EventCallback>?>(); // Add subscriber void on(eventName, EventCallback f) { _emap[eventName] ??= <EventCallback>[]; _emap[eventName]!.add(f); } // Remove subscriber void off(eventName, [EventCallback? f]) { var list = _emap[eventName]; if (eventName == null || list == null) return; if (f == null) { _emap[eventName] = null; } else { list.remove(f); } } // Trigger event; all subscribers of the event will be called void emit(eventName, [arg]) { var list = _emap[eventName]; if (list == null) return; int len = list.length - 1; // Traverse backward to prevent index errors caused by subscribers removing themselves during callbacks for (var i = len; i > -1; --i) { list[i](arg); } } } // Define a top-level (global) variable; pages can use 'bus' after importing this file var bus = EventBus();
Usage Example:
In Page A:
// Listen for login events bus.on("login", (arg) { // do something });
In Login Page B:
// Trigger login event after successful login; subscribers in Page A will be called bus.emit("login", userInfo);
Note: The standard way to implement the singleton pattern in Dart is by using a static variable along with a factory constructor. This ensures that EventBus()
always returns the same instance, and readers should understand and master this method.
Event buses are typically used for state sharing between components. However, there are also dedicated packages for state sharing between components, such as Redux, MobX, and the previously introduced Provider. For simpler applications, an event bus is often sufficient to meet business needs. If you decide to use a state management package, carefully consider whether your app truly needs it to avoid overcomplication and excessive design.