Flutter进阶(5):EventBus全局事件总线
一、EventBus的基本概念
Flutter EventBus 是一种用于在 Flutter 应用程序中实现组件间通信的事件总线机制。可以用于在应用程序中实现各个组件之间的通信。它基于发布/订阅模式,允许组件订阅感兴趣的事件,并在事件发生时接收通知。
二、Flutter EventBus的工作原理
Flutter EventBus 的工作原理主要包括三个核心组件:事件、事件总线和订阅者。
- 事件:事件是在应用程序中不同组件之间传递的消息对象。每个事件都是一个单独的类,它包含携带的数据和其他相关信息。
- 事件总线:事件总线是事件发布和订阅的中介者。它维护了一个事件订阅者的列表,并允许发布者将事件发布到总线上。
- 订阅者:订阅者是对特定事件感兴趣的组件。它们通过向事件总线注册自己,以便在事件发生时接收通知。
当发布者发布一个事件时,事件总线会将该事件广播给所有订阅了该事件的订阅者。订阅者接收到事件后,会执行相应的回调函数来处理该事件。
三、Flutter EventBus如何用于组件间通信
Flutter EventBus 通过以下步骤实现组件间通信:
(1)实例化EventBus:在应用程序中创建一个全局的 EventBus 实例。
(2)定义事件类:创建表示不同事件类型的类。
(3)订阅事件:在需要接收事件通知的组件中,通过 EventBus 实例的on
方法订阅感兴趣的事件。
(4)发布事件:在某个组件中发生事件时,通过 EventBus 实例的fire
方法发布该事件。
(5)处理事件:订阅了事件的组件会接收到通知,并执行相应的回调函数来处理该事件。
四、Flutter EventBus的使用场景
Flutter EventBus 通常用于以下场景:
- 跨页面通信:在不同页面之间传递数据或通知状态变化。
- 组件解耦:通过事件总线实现组件之间的松耦合,降低组件之间的依赖关系。
- 广播机制:实现全局广播机制,允许多个组件同时监听和处理同一事件。
五、Flutter EventBus的优缺点及注意事项
优点:
- 简化组件间通信:通过事件总线,可以简化组件之间的通信流程,降低代码复杂度。
- 降低耦合度:事件总线实现了发布者和订阅者之间的解耦,使得组件更加独立和可维护。
- 易于扩展:可以方便地添加新的事件类型和订阅者,无需修改现有代码。
缺点:
- 调试困难:由于事件总线的异步和广播特性,可能会导致调试过程中难以追踪事件流。
- 内存泄漏风险:如果订阅者没有正确取消订阅,可能会导致内存泄漏问题。
注意事项:
- 避免滥用:虽然 EventBus 提供了一种方便的组件间通信方式,但应避免滥用导致代码结构混乱。
- 正确管理订阅:确保在组件销毁时取消订阅,避免内存泄漏。
- 合理设计事件类型:合理设计事件类型,避免事件过于复杂或冗余。
六、基本示例:处理多种类型的事件
先导入EventBus依赖:
dependencies:
event_bus: ^2.0.0
(1)实例化EventBus:在应用程序中创建一个全局的 EventBus 实例。
import 'package:event_bus/event_bus.dart';
// 全局 EventBus 对象
EventBus eventBus = EventBus();
(2)定义事件类:创建表示不同事件类型的类。
// 定义事件类
// 定义登录事件
class LoginEvent {
final String username;
LoginEvent(this.username);
}
// 定义注销事件
class LogoutEvent {}
// 定义消息事件
class MessageEvent {
final String message;
MessageEvent(this.message);
}
(3)订阅事件:在需要接收事件通知的组件中,通过 EventBus 实例的on
方法订阅感兴趣的事件。
void main() {
// 订阅事件:LoginEvent
eventBus.on<LoginEvent>().listen((event) {
debugPrint("User logged in: ${event.username}");
});
// 订阅事件:LogoutEvent
eventBus.on<LogoutEvent>().listen((_) {
debugPrint("User logged out");
});
// 订阅事件:MessageEvent
eventBus.on<MessageEvent>().listen((event) {
debugPrint("Message received: ${event.message}");
});
}
(4)发布事件:在某个组件中发生事件时,通过 EventBus 实例的fire
方法发布该事件。
void main() {
// ......
// 模拟发布事件
eventBus.fire(LoginEvent("JohnDoe"));
eventBus.fire(MessageEvent("Hello, EventBus!"));
eventBus.fire(LogoutEvent());
}
(5)处理事件:订阅了事件的组件会接收到通知,并执行相应的回调函数来处理该事件。即下面的listen
监听函数:
eventBus.on<LoginEvent>().listen((event) {
debugPrint("User logged in: ${event.username}");
});
全部代码如下:
import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
// 全局 EventBus 对象
EventBus eventBus = EventBus();
// 定义事件类
// 定义登录事件
class LoginEvent {
final String username;
LoginEvent(this.username);
}
// 定义注销事件
class LogoutEvent {}
// 定义消息事件
class MessageEvent {
final String message;
MessageEvent(this.message);
}
void main() {
// 订阅事件:LoginEvent
eventBus.on<LoginEvent>().listen((event) {
debugPrint("User logged in: ${event.username}");
});
// 订阅事件:LogoutEvent
eventBus.on<LogoutEvent>().listen((_) {
debugPrint("User logged out");
});
// 订阅事件:MessageEvent
eventBus.on<MessageEvent>().listen((event) {
debugPrint("Message received: ${event.message}");
});
// 模拟发布事件
eventBus.fire(LoginEvent("JohnDoe"));
eventBus.fire(MessageEvent("Hello, EventBus!"));
eventBus.fire(LogoutEvent());
}
输出:
flutter: User logged in: JohnDoe
flutter: Message received: Hello, EventBus!
flutter: User logged out
七、常见问题
(1) 如何确保事件处理的顺序?
- 默认情况下,EventBus 按照事件发布的顺序依次触发订阅者。
- 如果需要并发或异步处理,可以在订阅逻辑中使用 Future 或 Stream。
(2) 订阅者是否会收到所有事件?
不会,只有订阅的事件类型匹配时,订阅者才会被触发。例如:
eventBus.on<LoginEvent>()
只会响 LoginEvent
。
(3) 多个订阅者对同一个事件类型的处理?
一个事件可以被多个订阅者响应,每个订阅者的处理逻辑互不干扰。
(4) 是否支持动态事件类型?
支持,但不推荐。例如:
eventBus.on<dynamic>().listen((event) {
debugPrint("Event received: $event");
});
这样会导致所有事件都被接收,难以管理。
八、扩展:使用单例模式的示例
下面我们实现一个简单的全局事件总线,使用单例模式。
核心原理就:单例 + Map<事件Key,订阅者列表> + 列表遍历,一个简单的实现代码示例如下:
(1)实现EventBus.dart
:
// ignore_for_file: file_names, prefer_generic_function_type_aliases
// 订阅者回调签名
typedef void EventCallback(arg);
class EventBus {
// 私有构造函数
EventBus._internal();
// 保存单例
static final EventBus _singleton = EventBus._internal();
// 工厂构造函数
factory EventBus()=> _singleton;
// 保存事件订阅者队列,key:事件名(id),value: 对应事件的订阅者队列
final _emap = <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);
}
}
// 触发事件,事件触发后该事件所有订阅者会被调用(arg的类型为String)
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 eventBus = EventBus();
(2)实现main.dart
,在main
函数内监听登录事件:
import 'package:flutter/material.dart';
import 'routes.dart'; // 添加路由页面
import 'EventBus.dart';
main(List<String> args) {
// 监听登录事件
eventBus.on("login", (arg) {
debugPrint(arg);
});
// 监听登录事件
eventBus.on("message", (arg) {
debugPrint(arg);
});
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo', // 标题
debugShowCheckedModeBanner: false, // 不显示debug标识
theme: ThemeData(
// 主题
primarySwatch: Colors.blue,
),
routes: routes, // 指定路由页面
initialRoute: 'home', // 指定路由导航的首页名称
);
}
}
(3)实现LoginDemo.dart
,在登录按钮的响应函数内订阅事件:
// ignore_for_file: file_names
import 'package:flutter/material.dart';
import 'EventBus.dart';
class LoginDemo extends StatelessWidget {
const LoginDemo({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("LoginDemo"),
),
body: const MyHomeBody(),
);
}
}
class MyHomeBody extends StatelessWidget {
const MyHomeBody({super.key});
@override
Widget build(BuildContext context) {
return Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly, // 主轴对齐方式:均匀分布
crossAxisAlignment: CrossAxisAlignment.center, // 交叉轴对齐方式:居中对齐
children: <Widget>[
const TextField(
autofocus: true,
decoration: InputDecoration(
labelText: "用户名",
hintText: "用户名或邮箱",
prefixIcon: Icon(Icons.person)),
),
const TextField(
decoration: InputDecoration(
labelText: "密码",
hintText: "您的登录密码",
prefixIcon: Icon(Icons.lock)),
obscureText: true,
),
ElevatedButton(
style: ButtonStyle(
backgroundColor: WidgetStateProperty.all(Colors.blue), // 按扭背景颜色
foregroundColor: WidgetStateProperty.all(Colors.white), // 按钮文本颜色
shape: WidgetStateProperty.all(RoundedRectangleBorder(
borderRadius: BorderRadius.circular(10))), // 圆角
),
child: const Text("登录"),
onPressed: () {
// 登录页中,登录成功后触发登录事件,其它页面订阅者会被调用
eventBus.emit("login", true.toString());
// 登录页中,登录成功后触发登录事件,其它页面订阅者会被调用
eventBus.emit("message", 'login success');
},
),
],
);
}
}
(4)点击登录按钮:输出如下:
flutter: true
flutter: login success
参考:
Flutter EventBus_eventbus 取消订阅 flutter-CSDN博客
flutter插件event_bus(事件总线)封装 - 简书
Flutter学习笔记(34)--EventBus的使用 - CurtisWgh - 博客园
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库