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 - 博客园


posted @   fengMisaka  阅读(183)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示