Stream: https://www.yuque.com/zhuchaoyang/wcyoce/orhnkz#T6PJ8

全局事件总线

在APP中,我们经常会需要一个广播机制,用以跨页面事件通知,比如一个需要登录的APP中,页面会关注用户登录或注销事件,来进行一些状态更新。这时候,一个事件总线便会非常有用,事件总线通常实现了订阅者模式,订阅者模式包含发布者和订阅者两种角色,可以通过事件总线来触发事件和监听事件,本节我们实现一个简单的全局事件总线,我们使用单例模式,代码如下:

lib/services/bus_login.dart
  1. // 订阅者回调签名
  2. // typedef 给某一种特定的函数类型起了一个名字,可以认为是一个类型的别名,
  3. // 可以类比class和对象这样理解:自己定义了一种数据类型,
  4. // 不过这种数据类型是函数类型,一个一个的具体实现的函数就相当于按照这种类型实例化的对象会有类型检查
  5. typedef EventCallback(arg);
  6. class EventBusLogin {
  7. // 命名构造函数
  8. EventBusLogin._internal() {}
  9. // 保存单例
  10. static EventBusLogin _instance = EventBusLogin._internal();
  11. // 工厂构造函数
  12. factory EventBusLogin() => _instance;
  13. // 保存事件订阅者队列,key: 事件名(id), value: 对应事件的订阅者队列
  14. var _emap = Map<Object, List<EventCallback>>();
  15. // 添加订阅者
  16. on(eventName, EventCallback fn) {
  17. if (eventName == null || fn == null) return;
  18. _emap[eventName] ??= List<EventCallback>();
  19. _emap[eventName].add(fn);
  20. }
  21. // 移除订阅者
  22. off(eventName, [EventCallback fn]) {
  23. var list = _emap[eventName];
  24. if (eventName == null || list == null) return;
  25. if (fn == null) {
  26. _emap[eventName] = null;
  27. } else {
  28. list.remove(fn);
  29. }
  30. }
  31. // 触发事件,事件触发后该事件所有订阅者会被调用
  32. emit(eventName, [arg]) {
  33. var list = _emap[eventName];
  34. if (list == null) return;
  35. // 反向遍历,防止订阅者在回调中移除自身带来的下标错位
  36. int len = list.length - 1;
  37. for (var i = len; i > -1; --i) {
  38. list[i](arg);
  39. }
  40. }
  41. }
  42. // 定义一个 top-level(全局)变量,页面引入该文件后可以直接使用 busLogin
  43. var busLogin = EventBusLogin();

A 页面
  1. import 'package:app1/services/bus_login.dart';
  2. @override
  3. void initState() {
  4. super.initState();
  5. //监听登录事件
  6. busLogin.on('login', (arg) {
  7. print('test-login => $arg'); //test-login => {id: 1, username: 朱朝阳}
  8. });
  9. }

login页面
  1. //登录成功后,触发登录事件,页面A中订阅者会被调用
  2. busLogin.emit('login', {
  3. 'id': 1,
  4. 'username': '朱朝阳',
  5. });

注意:Dart中实现单例模式的标准做法就是使用static变量+工厂构造函数的方式,这样就可以保证new EventBus()始终返回都是同一个实例,读者应该理解并掌握这种方法。

事件总线通常用于组件之间状态共享,但关于组件之间状态共享也有一些专门的包如redux、以及前面介绍过的Provider。对于一些简单的应用,事件总线是足以满足业务需求的,如果你决定使用状态管理包的话,一定要想清楚您的APP是否真的有必要使用它,防止“化简为繁”、过度设计。

库 event_bus

https://pub.dev/packages/event_bus

安装依赖

  1. dependencies:
  2. event_bus: ^1.1.1

导入

  1. import 'package:event_bus/event_bus.dart';

使用

定义event_bus服务
  1. import 'package:event_bus/event_bus.dart';
  2. // 初始化 bus
  3. EventBus eventBus = EventBus();
  4. // 登录成功事件(自定义广播数据)
  5. class LoginSucEvent {
  6. Map<String, dynamic> userInfo;
  7. LoginSucEvent(this.userInfo);
  8. }

触发事件
  1. eventBus.fire(LoginSucEvent({
  2. 'id': 1,
  3. 'userInfo': '朱朝阳',
  4. }));

监听事件
  1. @override
  2. void initState() {
  3. super.initState();
  4. // 监听指定事件
  5. eventBus.on<LoginSucEvent>().listen((event) {
  6. print('LoginSucEvent => ${event.userInfo}'); //LoginSucEvent => {id: 1, userInfo: 朱朝阳}
  7. // 监听到登录成功广播,接着做登录成功后的操作
  8. // ...
  9. });
  10. // 监听所有事件
  11. eventBus.on().listen((event) {
  12. print(event.runtimeType); //LoginSucEvent
  13. print('LoginSucEvent => ${event.userInfo}'); //LoginSucEvent => {id: 1, userInfo: 朱朝阳}
  14. });
  15. }

销毁事件
  1. eventBus.destroy();

源码

  1. import 'dart:async';
  2. class EventBus {
  3. //StreamController 是 Stream 的一个帮助类,可用于整个 Stream 过程的控制。
  4. StreamController _streamController;
  5. StreamController get streamController => _streamController;
  6. EventBus({bool sync = false})
  7. : _streamController = StreamController.broadcast(sync: sync);
  8. EventBus.customController(StreamController controller)
  9. : _streamController = controller;
  10. Stream<T> on<T>() {
  11. if (T == dynamic) {
  12. return streamController.stream;
  13. } else {
  14. return streamController.stream.where((event) => event is T).cast<T>();
  15. }
  16. }
  17. void fire(event) {
  18. streamController.add(event);
  19. }
  20. void destroy() {
  21. _streamController.close();
  22. }
  23. }