Flutter (60): Event Bus

Time: Column:Mobile & Frontend views:228

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.