構(gòu)建復(fù)雜應(yīng)用的神器,F(xiàn)Broadcast
FWidget 用心提供精致的組件,助您構(gòu)建精美的應(yīng)用。

FWidget 一直致力于為開發(fā)者們精心打造易于構(gòu)建精美應(yīng)用的 Widget。
迄今為止,FWidget 已經(jīng)收到了來自開發(fā)者們的 1215 個(gè) Star,感謝開發(fā)者們的支持,這對 FWidget 來說至關(guān)重要。
今天,FWidget 為開發(fā)者們帶來了一個(gè)全新的組件 FBroadcast,以幫助開發(fā)者們能夠更輕松的構(gòu)建更為復(fù)雜精美的應(yīng)用。
例如,在 FWidget 的官方網(wǎng)站 https://fwidget.cn 中,通過 FBroadcast 很輕松的就實(shí)現(xiàn)了成本極低的,且易于維護(hù)的全局實(shí)時(shí)語言切換。它十分簡單有效和明確!
? 特性
來看看 FBroadcast 為開發(fā)者提供了那些不可思議的能力支持:
支持發(fā)送和接收指定類型的消息
消息支持?jǐn)y帶任意類型數(shù)據(jù)包
提供環(huán)境注冊,一行代碼即可移除環(huán)境內(nèi)所有接收者
不可思議的粘性廣播
雙向通信支持
易于構(gòu)建簡單明確的局部和全局狀態(tài)管理

? 傳送區(qū)
? 【傳送門:FBroadcast Github 主頁】
? 【傳送門:FBroadcast 文檔】
? 獻(xiàn)給開發(fā)者的 FBroadcast
FBroadcast 是一套高效靈活的廣播系統(tǒng),可以幫助開發(fā)者輕松、有序的構(gòu)建具有極具復(fù)雜性的關(guān)聯(lián)交互和狀態(tài)變化的精美應(yīng)用。

FBroadcast 將為構(gòu)建復(fù)雜的精美應(yīng)用帶來那些顯而易見的改變呢?
Widget/模塊間的完全解耦
通過 FBroadcast 高效的廣播系統(tǒng),開發(fā)者可以輕易的完成Widget/模塊的解耦。在應(yīng)用構(gòu)建的時(shí)候,經(jīng)常需要 Widget/模塊A、B、C、.. 之間根據(jù)交互操作互相變更狀態(tài)或數(shù)據(jù),開發(fā)者們不得不為此讓各個(gè)Widget/模塊互相依賴或者為它們建立統(tǒng)一的狀態(tài)管理,這能解決問題,但這讓構(gòu)建變得麻煩,也讓變更變得難以進(jìn)行。
FBroadcast 通過建立起簡單、有效、明確廣播系統(tǒng),使得在任意Widget/模塊中任意時(shí)刻/位置的改變能夠主動(dòng)發(fā)出廣播,而需要根據(jù)這些變更作出響應(yīng)或更新視圖的Widget/模塊只需要注冊相應(yīng)的信息接收器,就可以在變更發(fā)生時(shí),接收到消息,作出響應(yīng)。這使得關(guān)聯(lián)模塊間不再需要互相依賴,或是為它們設(shè)計(jì)建立統(tǒng)一的狀態(tài)管理器。
十分簡單,輕量,和易于變更。當(dāng)一個(gè)Widget/模塊不在需要根據(jù)另一個(gè)Widget/模塊的變更而更新時(shí),只需移除其中的接收器即可,而不用為此而大改依賴關(guān)系或是狀態(tài)管理器。
簡單、靈活、明確、易管理
FBroadcast ?為開發(fā)者提供了可以在任意時(shí)刻發(fā)送廣播,和注冊/移除接收器的能力,毫無約束和靈活。
廣播和接收器之間通過明確的類型(字符串)來互相確認(rèn)身份,指定類型的廣播,只能被指定類型的接收器接收。
FBroadcast 提供了環(huán)境注冊支持,開發(fā)者可以在環(huán)境解構(gòu)時(shí),通過 [unregister()] 函數(shù)一次性移除環(huán)境中的所有類型接收器,而無需記憶和關(guān)心究竟需要移除那些接收器。例如,開發(fā)者可以在 Widget 的
dispose()中,將注冊在該 Widget 中的所有接收器一次性全部移除。借助現(xiàn)代IDEA的能力,開發(fā)者可以為廣播系統(tǒng)建立一張(或多張)統(tǒng)一的廣播類型索引表,通過IDEA的引用索引,開發(fā)者可以輕松的、一目了然的看到該類型的廣播在那些地方被發(fā)送過,在那些地方注冊了接收器,十分易于管理和維護(hù)。而使用字符串來作為類型標(biāo)識(shí),使得開發(fā)者可以將不同類型的廣播含義描述的足夠清晰明白。
粘性廣播支持
FBroadcast 提供了發(fā)送粘性廣播的支持。在還沒有注冊任何接收器的情況下,開發(fā)者可以在事件發(fā)生時(shí),預(yù)先發(fā)送一條粘性廣播。粘性廣播會(huì)被暫時(shí)滯留在廣播系統(tǒng)中,當(dāng)有接收器被注冊時(shí),即會(huì)立即廣播。這有助于幫助開發(fā)者在做邏輯設(shè)計(jì)時(shí)采取更清晰有效的思路。
例如,當(dāng)一個(gè)控制模塊中的開關(guān)按鈕被打開,而此時(shí)開關(guān)所控制的模塊還沒有被構(gòu)建,就可以先發(fā)送一條粘性廣播,在模塊被構(gòu)建完成注冊了接收器后,就會(huì)立即接收到粘性廣播而進(jìn)入開啟狀態(tài)(這與互相依賴、定義統(tǒng)一狀態(tài)管理或是參數(shù)傳遞,然后檢查開關(guān)狀態(tài)的思路有本質(zhì)區(qū)別)。
? 這真不是一般的 Broadcast
? Base Broadcast
通信就要簡單,明確
通過 FBroadcast 來注冊,發(fā)送廣播非常簡便。
/// 注冊接收器////// registerFBroadcast.instance().register(Key_Message, (value, callback) {/// do something});/// 發(fā)送消息////// send messageFBroadcast.instance().broadcast(Key_Message);
FBroadcast 允許開發(fā)者在發(fā)送消息的時(shí)候,帶有數(shù)據(jù)。
/// 注冊接收器////// registerFBroadcast.instance().register(Key_Message, (value, callback) {/// 獲取數(shù)據(jù)////// get datavar data = value;});/// 發(fā)送消息和數(shù)據(jù)////// send message and dataFBroadcast.instance().broadcast(/// 消息類型////// message typeKey_Message,/// 數(shù)據(jù)////// datavalue: data,);
開發(fā)者可以選擇將特定類型的消息進(jìn)行持久化,這樣就能輕易實(shí)現(xiàn)廣播式的全局狀態(tài)管理。
FBroadcast.instance().broadcast(/// 消息類型////// message typeKey_Message,/// 數(shù)據(jù)////// datavalue: data,/// 將消息類型持久化////// Persist the message typespersistence: true,);
當(dāng)開發(fā)者將一個(gè)消息類型持久化后,就可以在任意位置,通過 FBroadcast.value(String key) 來獲取廣播系統(tǒng)中該類型消息的最新的數(shù)據(jù)。而更新廣播系統(tǒng)中的數(shù)據(jù)只需要通過 broadcast() 即可完成。
??注意,一個(gè)消息類型一旦持久化就只能通過
FBroadcast.instance().clear(String key)來從廣播系統(tǒng)中移除該類型的消息。
? Sticky Broadcast
更多的選擇,構(gòu)建更精美的應(yīng)用
FBroadcast 支持開發(fā)者發(fā)送粘性廣播。
FBroadcast.instance().stickyBroadcast(/// 消息類型////// message typeKey_Message,/// 數(shù)據(jù)////// datavalue: data,);
當(dāng)廣播系統(tǒng)中沒有對應(yīng)類型的接收器時(shí),粘性廣播 將會(huì)暫時(shí)滯留在系統(tǒng)中,直到有該類型的接收器被注冊,則會(huì)立即發(fā)出廣播(當(dāng)廣播系統(tǒng)中有對應(yīng)類型的接收器時(shí),就和普通廣播具有相同的表現(xiàn))。
? Two-way communication
雙向溝通,雙倍效率
FBroadcast 支持在廣播發(fā)送點(diǎn)接收接收器返回的消息。
/// 發(fā)送消息////// send messageFBroadcast.instance().broadcast(/// 消息類型////// message typeKey_Message,/// 數(shù)據(jù)////// datavalue: data,/// 接收器返回的消息////// The message returned by the receivercallback: (value){// do something});/// 注冊接收器////// registerFBroadcast.instance().register(Key_Message, (value, callback) {/// 獲取數(shù)據(jù)////// get datavar data = value;/// do somethingvar result = logic();/// 返回消息////// return messagecallback(result);});
通過 FBroadcast 能夠給十分輕松的實(shí)現(xiàn)雙向通信。
? Bind Context
一碼卸載,快捷精準(zhǔn)
FBroadcast 支持在注冊接收器時(shí)傳入一個(gè)環(huán)境對象(可以是任意類型),這會(huì)將接收器注冊到環(huán)境中,當(dāng)環(huán)境解構(gòu)時(shí),開發(fā)者可以方便的一次性移除所有在該環(huán)境中注冊的接收器。
/// 注冊接收器////// registerFBroadcast.instance().register(/// 消息類型////// Message typeKey_Message1,/// Receiver////// Receiver(value, callback) {/// do something},/// 更多接收器////// more receivermore: {/// 消息類型:接收器////// Message type: ReceiverKey_Message2: (value, callback) {/// do something},Key_Message3: (value, callback) {/// do something},Key_Message4: (value, callback) {/// do something},},/// 環(huán)境對象////// contextcontext: this,);/// 移除環(huán)境中的所有接收器////// Remove all receivers from the environmentFBroadcast.instance().unregister(this);
? 使用 FBroadcast 可以做些什么?
? 消息傳遞
場景:點(diǎn)擊 Start,Runner 開始 Run,顯示屏需要實(shí)時(shí)更新運(yùn)動(dòng)員的狀態(tài)。

? 1. 創(chuàng)建 Runner:
/// Runnerclass Runner {Runner() {/// registerFBroadcast.instance().register(Key_RunnerState, (value, callback) {if (value is String && value.contains("Run")) {/// receive start run messageFBroadcast.instance().broadcast(Key_RunnerState, value: "0m..");run(20);}});}run(double distance) {/// send running messageTimer(Duration(milliseconds: 500), () {FBroadcast.instance().broadcast(Key_RunnerState, value: "${distance.toInt()}m..");var newDistance = distance + 20;if (newDistance > 100) {FBroadcast.instance().broadcast(Key_RunnerState, value: "Win!\nTotal time is 2.5s");} else {run(newDistance);}});}}
? 2. 創(chuàng)建 UI:
Column(mainAxisAlignment: MainAxisAlignment.center,children:[ Stateful(/// initinitState: (setState, data) {FBroadcast.instance().register(Key_RunnerState,(value, callback) {/// refresh uisetState(() {});},/// bind contextcontext: data,);},builder: (context, setState, data) {return FSuper(.../// get running messagetext: FBroadcast.value(Key_RunnerState) ?? "Preparing..",);},),const SizedBox(height: 100),FButton(text: "Start"...onPressed: () {/// send start run messageFBroadcast.instance().broadcast(Key_RunnerState, value: "Running...");},),],)
在上面的示例中,通過 FBroadcast 簡單清晰的實(shí)現(xiàn)了 Runner 和 UI 之間的通信。
點(diǎn)擊 Start 按鈕,通過 FBroadcast 發(fā)送起跑消息給 Runner;
Runner 收到消息后,開始 Run,同時(shí)不斷通過 FBroadcast 發(fā)出 Running info;
UI 由于注冊了接收器,在接收到 Running info 時(shí),通過 FBroadcast.value()獲取消息數(shù)據(jù),自動(dòng)更新視圖。
整個(gè)過程中,Runner 和 UI 之間是完全解耦的,且 UI 只需在 init 中注冊接收器(receiver 中調(diào)用 setState((){})),就能根據(jù)消息數(shù)據(jù)的變化,自動(dòng)實(shí)時(shí)的更新視圖,而無需開發(fā)者關(guān)心整個(gè)過程。
? 雙向通信
場景:點(diǎn)擊按鈕請求定位,定位成功后接收結(jié)果,刷新定位點(diǎn)

? 1. 全局定位服務(wù)提供商
class LocationServer {LocationServer() {init();}init() {/// register Key_Location receiverFBroadcast.instance().register(Key_Location, (value, callback) async {var loc = await location();/// return messagecallback(loc);});}/// Analog positioningFuture> location() async {
await Future.delayed(Duration(milliseconds: 2000));return [Random().nextDouble() * 280, Random().nextDouble() * 150];}}
? 2. 點(diǎn)擊發(fā)送定位請求,接收返回消息
FButton(...text: "Location",onPressed: () {FLoading.show(context,color: Colors.black26, loading: buildLoading());/// request locationFBroadcast.instance().broadcast(Key_Location,callback: (location) {/// The message returned by the receiversetState(() {FLoading.hide();this.location = location;});});},)
FBroadcast 能夠進(jìn)一步簡化需要雙向通信的場景。開發(fā)者可以看到,在這個(gè)例子中,通過 FBroadcast 能夠輕松的實(shí)現(xiàn)定位請求這種雙向通信的場景,而且使得定位服務(wù)提供商和UI實(shí)現(xiàn)的完全的解耦。
UI交互點(diǎn)只需要發(fā)送定位請求的廣播,任何注冊該廣播的定位服務(wù)提供商就可以接收該請求進(jìn)行處理,然后返回結(jié)果到UI交互點(diǎn)。也就是說,隨著項(xiàng)目的演進(jìn),開發(fā)者可以隨時(shí)提供新的定位服務(wù)提供商,而無需關(guān)心任何的UI變更。
? 局部狀態(tài)管理
場景:點(diǎn)擊改變UI顏色

? 1. 點(diǎn)擊發(fā)出事件
FButton(text: "Change Color",...onPressed: () {/// send change color messageFBroadcast.instance().broadcast(Key_Color, value: reduceColor());},)
? 2. UI 注冊接收器
Stateful(/// initinitState: (setState, data) {/// registerFBroadcast.instance().register(Key_Color,(value, callback) {/// refresh uisetState(() {});},/// bind contextcontext: data,);},builder: (context, setState, data) {return FSuper(.../// get color valuebackgroundColor: FBroadcast.value(Key_Color) ?? mainBackgroundColor, );},)
通過 FBroadcast 可以很輕易的完成 UI 交互之間的局部狀態(tài)更新。上面的示例展示了顏色的變更,數(shù)據(jù)對象只有一個(gè)參數(shù),實(shí)際開發(fā)過程中,開發(fā)者可以根據(jù)需要將通信的數(shù)據(jù)對象進(jìn)行豐富擴(kuò)展。
開發(fā)者只需要在需要更新 UI 的 Widget 中注冊接收器,調(diào)用一次 setState((){}),在交互點(diǎn)發(fā)出消息。而不用去主動(dòng)的將觸發(fā)邏輯和 setState((){}) 在所有的交互點(diǎn)都寫一次。
? 全局狀態(tài)管理
場景:點(diǎn)擊頭像跳轉(zhuǎn)登陸頁,當(dāng)賬號(hào)密碼不為 null 時(shí),登陸按鈕才可以點(diǎn)擊。點(diǎn)擊登陸按鈕發(fā)送登陸請求,登陸成功后,返回上一頁,刷新用戶信息。
? 1. 用戶信息Widget注冊接收器
class Avatar extends StatefulWidget {@override_AvatarState createState() => _AvatarState();}class _AvatarState extends State{ User user;int msgCount = 0;@overridevoid initState() {super.initState();FBroadcast.instance().register(Key_MsgCount,/// register Key_MsgCount reviver(value, callback) => setState(() {msgCount = value;}),more: {/// register Key_User reviverKey_User: (value, callback) => setState(() {/// get valueuser = value;}),},/// bind contextcontext: this,);}@overrideWidget build(BuildContext context) {return FSuper(...backgroundImage: (user == null || _textIsEmpty(user.avatar)) ? null : AssetImage(user.avatar),redPoint: user != null && msgCount > 0,redPointText: msgCount.toString(),text: user != null ? null : "Click Login",onClick: user != null? null: () => Navigator.push(context, MaterialPageRoute( builder: (context) => LoginPage())),);}@overridevoid dispose() {super.dispose();/// remove all receivers from the environmentFBroadcast.instance().unregister(this);}}
登陸頁中注冊 Key_User 接收器,當(dāng)接收到登陸消息時(shí),取出其中的數(shù)據(jù),刷新UI。
? 2. 構(gòu)建數(shù)據(jù)模型
class User{String name;String avatar;int messageCount = 0;String info;}
? 3. 構(gòu)建邏輯處理對象
class LoginHandler {String _userName;String _password;/// set user name, check to see if login is allowedset userName(String v) {_userName = v;if (_textNoEmpty(_userName) && _textNoEmpty(_password)) {FBroadcast.instance().broadcast(Key_Login, value: true);} else {FBroadcast.instance().broadcast(Key_Login, value: false);}}/// set user password, check to see if login is allowedset password(String v) {_password = v;if (_textNoEmpty(_userName) && _textNoEmpty(_password)) {FBroadcast.instance().broadcast(Key_Login, value: true);} else {FBroadcast.instance().broadcast(Key_Login, value: false);}}/// loginvoid login() {Timer(Duration(milliseconds: 1500), () {/// login success,send login success message —— Key_UserFBroadcast.instance().broadcast(Key_User,value: User()..avatar = "assets/logo.png"..name = _userName..info ="Seriously provide exquisite widget to help you build exquisite application.",/// Persistence Key_Userpersistence: true,);});}}
將邏輯處理轉(zhuǎn)移到 LoginHandler 中進(jìn)行隔離,所有的處理結(jié)果都通過 FBroadcast 廣播出去,使注冊到廣播系統(tǒng)中的對應(yīng)接收器能夠響應(yīng)。
? 4. 登陸頁
class LoginPage extends StatefulWidget {@override_LoginPageState createState() => _LoginPageState();}class _LoginPageState extends State{ /// Logic handlerLoginHandler handler = LoginHandler();/// input controllerFSearchController _controller1 = FSearchController();FSearchController _controller2 = FSearchController();@overridevoid initState() {super.initState();_controller1.setListener(() {/// update userNamehandler.userName = _controller1.text;});_controller2.setListener(() {/// update passwordhandler.password = _controller2.text;});}@overrideWidget build(BuildContext context) {return {.../// userName inputFSearch(controller: _controller1,...),.../// userName inputFSearch(controller: _controller2,...),...Stateful(initState: (setState, data) {/// register login receiverFBroadcast.instance().register(Key_Login,/// refresh ui(value, callback) => setState(() {}),more: {/// register user receiverKey_User: (value, callback) {FLoading.hide();Navigator.pop(context);},},/// bind contextcontext: data,);},builder: (context, setState, data) {return FButton(...text: "LOGIN",/// Key_Login value=true is allowed to click loginonPressed: !(FBroadcast.value(Key_Login) ?? false)? null: () {_controller1.clearFocus();_controller2.clearFocus();FLoading.show(context);/// Execute login logichandler.login();},);},),...};}}
注冊接收器時(shí),只需在接收回調(diào)中調(diào)用 setState((){}),后續(xù)所有的數(shù)據(jù)變化刷新,開發(fā)者就可以不用關(guān)注了。而給 UI 賦值可以方便的通過 FBroadcast.value() 獲取對應(yīng)數(shù)據(jù)來進(jìn)行。
??注意,對于需要全局使用的狀態(tài)/數(shù)據(jù)模型,它們對應(yīng)的廣播類型,在發(fā)送時(shí),需要至少有一次將 persistence 設(shè)置為 true。上面示例中,就在登陸成功后,對
Key_User類型的廣播進(jìn)行了持久化。
/// login success,send login success message —— Key_UserFBroadcast.instance().broadcast(Key_User,value: User()..avatar = "assets/logo.png"..name = _userName..info ="Seriously provide exquisite widget to help you build exquisite application.",/// Persistence Key_Userpersistence: true,);
上面的示例中展示了通過 FBroadcast 可以輕松快速的實(shí)現(xiàn)消息傳遞,進(jìn)行 局部、全局狀態(tài)管理和刷新,很好的將各個(gè)模塊,邏輯以及UI 進(jìn)行解耦。FBroadcast 提供了簡潔易懂,而且十分靈活的廣播系統(tǒng),極少的束縛讓開發(fā)者可以快速上手,輕松實(shí)現(xiàn)復(fù)雜邏輯的簡化,幫助開發(fā)者構(gòu)建出易于維護(hù)的、復(fù)雜的、精美的應(yīng)用。
FBroadcast 在使用過程中,配合統(tǒng)一的廣播類型注冊表(也可以按模塊分多張),開發(fā)者可以很輕易的借助 IDEA 的引用檢索能力,隨時(shí)查看所有廣播的情況,對于不斷迭代過程中的維護(hù)十分有益。
想要了解更多詳細(xì)內(nèi)容?請?jiān)L問 FBroadcast 官方主頁 (PS:別忘了投出一個(gè)你認(rèn)可的 Star 哦 ?)。
? 如何使用?
在項(xiàng)目 pubspec.yaml 文件中添加依賴:
? pub 依賴方式
dependencies:fbroadcast: ^<版本號(hào)>
?? 注意,請到 pub 獲取 FBroadcast 最新版本號(hào)
? git 依賴方式
dependencies:fbroadcast:git:url: '[email protected]:Fliggy-Mobile/fbroadcast.git'ref: '<分支號(hào) 或 tag>'
?? 注意,分支號(hào) 或 tag 請以 FBroadcast 官方項(xiàng)目為準(zhǔn)。

感覺還不錯(cuò)?請到 《FBroadcast》的 Github 主頁投出您認(rèn)可的一個(gè) Star ? 吧!
更多精彩組件
《FSuper》- 幫助開發(fā)者快速構(gòu)建精美的復(fù)雜視圖
《FButton》- 為開發(fā)者準(zhǔn)備了諸多美妙的配置項(xiàng)
《FSwitch》- 具有優(yōu)良交互和視效的精美開關(guān)元素
《FRadio》- 一個(gè)適用于幾乎任意單選場景的單選組件
《FFloat》- 滿足你對浮動(dòng)元素的一切想象
《FRefresh》- 輕松構(gòu)建下拉刷新效果
《FDottedLine》- 輝煌的虛線效果
《FSearch》- 一應(yīng)俱全的搜索框組件
《FToast》- 精致靈活的 Flutter 原生 Toast 組件
《FLoading》- 幫助開發(fā)者自由的使用 Loading
