介紹
隨著每個工程的MVC
模組逐漸增多,模組與模組之間的通訊也變得越來越多,程式碼耦合必然增加。
Event Bus 為解耦而生,熱別是設計模式為MVC
或MVP
的專案。
原始碼
import 'dart:async';
class EventBus {
StreamController _streamController;
/// Controller for the event bus stream.
StreamController get streamController => _streamController;
/// Creates an [EventBus].
EventBus({bool sync = false})
: _streamController = StreamController.broadcast(sync: sync);
/// Instead of using the default [StreamController] you can use this constructor
EventBus.customController(StreamController controller)
: _streamController = controller;
/// Listens for events of Type [T] and its subtypes.
Stream<T> on<T>() {
if (T == dynamic) {
return streamController.stream;
} else {
return streamController.stream.where((event) => event is T).cast<T>();
}
}
/// Fires a new event on the event bus with the specified [event].
void fire(event) {
streamController.add(event);
}
/// Destroy this [EventBus]. This is generally only in a testing context.
void destroy() {
_streamController.close();
}
}
複製程式碼
是的你沒有看錯,原始碼就只有這些。EventBus
之所以這樣簡潔得益於Dart
中優秀的Stream
。Stream
的用法還是很多的,Event Bus
中主要使用Stream
的listen()
方法。
從原始碼很容易看出Event bus
的用法:
初始化
EventBus({bool sync = false})
: _streamController = StreamController.broadcast(sync: sync);
複製程式碼
streamController
作為Dart
官方內建類,實際上就是stream
資料的控制器。
sync
引數代表事件流是否立刻傳遞到監聽者,預設為false
。
broadcast
這種廣播的初始化方式可以讓stream
被多個人訂閱,如果你只想被一個人訂閱請使用StreamController
對應single
的初始化方式,這裡不過多討論。
EventBus
也支援你使用EventBus.customController(StreamController controller)
的方式自定義StreamController
。
監聽
監聽方式為eventBus.on<T>().listen()
,listen()
方法是內建類Stream
的API
。
Stream<T> on<T>() {
if (T == dynamic) {
return streamController.stream;
} else {
return streamController.stream.where((event) => event is T).cast<T>();
}
}
複製程式碼
StreamSubscription<T> listen(void onData(T event),
{Function onError, void onDone(), bool cancelOnError});
複製程式碼
Event Bus
通過泛型過濾,你可以只處理自己定義的事件型別。每當streamController
新增你監聽的事件時,監聽回撥將會執行。listen()
方法會返回一個StreamSubscription
,如果你想取消監聽可以呼叫對應的cancel()
方法
觸發事件
void fire(event) {
streamController.add(event);
}
複製程式碼
觸發事件比較簡單,streamController
呼叫add()
方法給Stream
新增事件,新增完成後廣播給所有對應監聽者。
實戰
這裡我們使用Event Bus
實現一個變換主題顏色的功能,先看下效果:
初始化
dependencies:
event_bus: ^1.1.1
複製程式碼
新增依賴之後別忘記flutter pub get
Coding
先定義一個主題顏色的事件:
import 'package:flutter/material.dart';
class ThemeColor {
final Color color;
ThemeColor(this.color);
}
複製程式碼
然後專案首頁初始化一個eventBus
,並在initState
時監聽ThemeColor
事件。
import 'package:flutter/material.dart';
import 'package:event_bus/event_bus.dart';
import 'package:flutterdemo/events/theme_color.dart';
void main() => runApp(MyApp());
EventBus eventBus = EventBus(); // 初始化 EventBus
class MyApp extends StatefulWidget {
MyApp({Key key}) : super(key: key);
@override
_MyAppState createState() => _MyAppState();
}
class _MyAppState extends State<MyApp> {
Color _themeColor;
@override
void initState() {
super.initState();
// 監聽 ThemeColor 事件
eventBus.on<ThemeColor>().listen((event) {
setState(() {
_themeColor = event.color;
});
});
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primaryColor: _themeColor, // 事件回撥的顏色賦值給 ThemeData
),
);
}
}
複製程式碼
定義一套主題顏色,在更改顏色時傳送ThemeColor
事件。傳送後監聽的回撥方法裡重新設定主題顏色。
import 'package:flutter/material.dart';
import 'package:flutterdemo/components/common_app_bar.dart';
import 'package:flutterdemo/events/theme_color.dart';
import 'package:flutterdemo/main.dart';
class PersonSetting extends StatefulWidget {
PersonSetting({Key key}) : super(key: key);
@override
_PersonSettingState createState() => _PersonSettingState();
}
class _PersonSettingState extends State<PersonSetting> {
Color _themeColor;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: CommonAppBar(title: '設定',),
body: Center(
child: DropdownButton(
value: _themeColor,
items: <DropdownMenuItem>[
DropdownMenuItem(value: Color(0xFF2196F3), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF2196F3),),),),
DropdownMenuItem(value: Color(0xFFE3F2FD), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFFE3F2FD),),),),
DropdownMenuItem(value: Color(0xFFBBDEFB), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFFBBDEFB),),),),
DropdownMenuItem(value: Color(0xFF90CAF9), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF90CAF9),),),),
DropdownMenuItem(value: Color(0xFF64B5F6), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF64B5F6),),),),
DropdownMenuItem(value: Color(0xFF42A5F5), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF42A5F5),),),),
DropdownMenuItem(value: Color(0xFF1E88E5), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1E88E5),),),),
DropdownMenuItem(value: Color(0xFF1976D2), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1976D2),),),),
DropdownMenuItem(value: Color(0xFF1565C0), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF1565C0),),),),
DropdownMenuItem(value: Color(0xFF0D47A1), child: SizedBox(width: 80, height: 30, child: Container(color: Color(0xFF0D47A1),),),),
],
onChanged: (color) {
eventBus.fire(ThemeColor(color)); // 傳送事件
setState(() {
_themeColor = color;
});
},
),
),
);
}
}
複製程式碼
其實Event Bus
部分的程式碼很少,耦合性也很低,這裡大部分的程式碼都是UI
佈局。
總結
Event Bus
使用起來很簡單,常規的用法就是定義事件,監聽事件,傳送事件,如何取消監聽上面也已經提到了。當然你也可以自定義streamController
做更多的事情,這裡只是拋磚引玉簡單的替換了主題顏色。
當然更改主題顏色使用其他方案也可以實現,如何使用Event Bus
大家也可以參考官方的 example,歸根結底都是為了減少程式碼之間的依賴從而降低程式碼的耦合度。