在 App 中,我们经常会需要一个广播机制,用以跨页面事件通知,比如一个需要登录的 App 中,页面会关注用户登录或注销事件,来进行一些状态更新。这时候,一个事件总线便会非常有用,事件总线通常实现了订阅者模式,订阅者模式包含发布者和订阅者两种角色,可以通过事件总线(event_bus)来触发事件和监听事件
1. EventBus
1.1 event_bus 引入
dependencies:
event_bus: ^2.0.0 #请使用pub上的最新版本
执行 flutter pub get命令,即可导入event_bus三方库
1.2 event_bus 基本使用
1. 创建全局的EventBus对象
// 1.创建全局的eventBus
final EventBus eventBus = EventBus();
2. 创建监听事件类型
// 2.创建监听事件类型
class UserInfoEvent {
UserInfoEvent(this.name, this.level);
String name;
int level;
}
3. 发送事件
// 3. 发送事件 eventBus.fire
eventBus.fire(UserInfoEvent("mshi", 11));
4. 监听事件
// 4. 监听事件
eventBus.on<UserInfoEvent>().listen((event) {
setState(() {
_text = "${event.name} -- ${event.level}";
});
});
示例
class MSEventBusDemo1 extends StatefulWidget {
const MSEventBusDemo1({Key? key}) : super(key: key);
@override
State<MSEventBusDemo1> createState() => _MSEventBusDemo1State();
}
class _MSEventBusDemo1State extends State<MSEventBusDemo1> {
String _text = "";
@override
void initState() {
// 4. 监听事件
eventBus.on<UserInfoEvent>().listen((event) {
setState(() {
_text = "${event.name} -- ${event.level}";
});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("EventBusDemo")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 3. 发送事件 eventBus.fire
eventBus.fire(UserInfoEvent("mshi", 11));
},
child: Text("点击"),
),
Text(_text, textScaleFactor: 1.5),
],
),
),
);
}
}
![](https://img.haomeiwen.com/i4837500/fbcfda03765c0a99.png)
2. 自定义EventBus
我们实现一个简单的全局事件总线,我们使用单例模式,代码如下
//订阅者回调签名
typedef void EventCallback(arg);
class EventBus {
//私有构造函数
EventBus._internal();
//保存单例
static EventBus _singleton = EventBus._internal();
//工厂构造函数
factory EventBus()=> _singleton;
//保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列
final _emap = Map<Object, List<EventCallback>?>();
//添加订阅者
void on(eventName, EventCallback f) {
_emap[eventName] ??= <EventCallback>[];
_emap[eventName]!.add(f);
}
//移除订阅者
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);
}
}
//触发事件,事件触发后该事件所有订阅者会被调用
void emit(eventName, [arg]) {
var list = _emap[eventName];
if (list == null) return;
int len = list.length - 1;
//反向遍历,防止订阅者在回调中移除自身带来的下标错位
for (var i = len; i > -1; --i) {
list[i](arg);
}
}
}
//定义一个top-level(全局)变量,页面引入该文件后可以直接使用bus
var bus = EventBus();
使用示例:
//页面A中
...
//监听登录事件
bus.on("login", (arg) {
// do something
});
//登录页B中
...
//登录成功后触发登录事件,页面A中订阅者会被调用
bus.emit("login", userInfo);
注意:Dart中实现单例模式的标准做法就是使用static变量+工厂构造函数的方式,这样就可以保证EventBus()始终返回都是同一个实例,读者应该理解并掌握这种方法。
示例
var customBus = CustomEventBus();
// 订阅者回调签名
typedef void EventCallback(arg);
class CustomEventBus {
// 私有构造函数
CustomEventBus._internal();
// 保存单例
static CustomEventBus _singleton = CustomEventBus._internal();
// 工厂构造函数
factory CustomEventBus() => _singleton;
//保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列
final _emap = Map<Object, List<EventCallback>?>();
// 添加订阅者
void on(eventName, EventCallback f) {
_emap[eventName] ??= <EventCallback>[];
_emap[eventName]!.add(f);
}
// 移除订阅者
void remove(eventName, [EventCallback? f]) {
var list = _emap[eventName];
if (eventName == null || list == null) {
return;
}
if (f == null) {
_emap[eventName] = null;
} else {
list.remove(f);
}
}
// 触发事件
void emit(eventName, [arg]) {
var list = _emap[eventName];
if (list == null) {
return;
}
int len = list.length - 1;
//反向遍历,防止订阅者在回调中移除自身带来的下标错位
for (var i = len; i > -1; --i) {
list[i](arg);
}
}
}
class MSCustomEevntBusDemo extends StatefulWidget {
const MSCustomEevntBusDemo({Key? key}) : super(key: key);
@override
State<MSCustomEevntBusDemo> createState() => _MSCustomEevntBusDemoState();
}
class _MSCustomEevntBusDemoState extends State<MSCustomEevntBusDemo> {
String _text = "";
@override
void initState() {
// 监听消息
customBus.on("MSClickEvent", (arg) {
if (arg is String) {
_text = arg;
setState(() {});
}
});
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("MSCustomEevntBusDemo")),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 发送消息
customBus.emit("MSClickEvent", "哈哈");
},
child: Text("点击")),
Text(
_text,
textScaleFactor: 1.5,
),
],
),
),
);
}
}
![](https://img.haomeiwen.com/i4837500/d1351b0faad132de.png)
总结
事件总线通常用于组件之间状态共享,但关于组件之间状态共享也有一些专门的包如redux、mobx以及Provider。对于一些简单的应用,事件总线是足以满足业务需求的,如果你决定使用状态管理包的话,一定要想清楚您的 App 是否真的有必要使用它,防止“化简为繁”、过度设计
网友评论