乐趣区

关于flutter:构建复杂应用的神器FBroadcast

FWidget 用心提供粗劣的组件,助您构建精美的利用。

FWidget 始终致力于为开发者们精心打造易于构建精美利用的 Widget

迄今为止,FWidget 曾经收到了来自开发者们的 1215Star,感激开发者们的反对,这对 FWidget 来说 至关重要

明天,FWidget 为开发者们带来了一个全新的组件 FBroadcast,以帮忙开发者们可能更轻松的构建更为简单精美的利用。

例如,在 FWidget 的官方网站 https://fwidget.cn 中,通过 FBroadcast 很轻松的就实现了老本极低的,且易于保护的 全局实时语言切换。它非常简略无效和明确!

✨ 个性

来看看 FBroadcast 为开发者提供了那些不堪设想的能力反对:

  • 反对 发送 接管 指定类型的音讯
  • 音讯反对携带 任意类型 数据包
  • 提供 环境注册,一行代码即可移除环境内所有接收者
  • 不堪设想的 粘性播送
  • 双向通信 反对
  • 易于构建简略明确的部分和全局状态治理

???? 传送区

????【传送门:FBroadcast Github 主页】

????【传送门:FBroadcast 文档】

???? 献给开发者的 FBroadcast

FBroadcast 是一套高效灵便的 广播系统 ,能够帮忙开发者 轻松、有序 的构建具备极具复杂性的 关联交互 状态变动 的精美利用。

FBroadcast 将为构建简单的精美利用带来那些不言而喻的扭转呢?

  • Widget/ 模块间的齐全解耦

    通过 FBroadcast 高效的广播系统,开发者能够轻易的实现 Widget/ 模块 的解耦。在利用构建的时候,常常须要 Widget/ 模块 A、B、C、.. 之间依据交互操作相互变更状态或数据,开发者们不得不为此让各个 Widget/ 模块相互依赖 或者为它们建设 对立的状态治理,这能解决问题,但这让构建变得麻烦,也让变更变得难以进行。

    FBroadcast 通过建设起 简略、无效、明确 广播系统,使得在任意 Widget/ 模块任意时刻 / 地位 的扭转可能被动收回播送,而须要依据这些变更作出 响应或更新视图 Widget/ 模块 只须要注册相应的 信息接收器,就能够在变更产生时,接管到音讯,作出响应。这使得关联模块间不再须要相互依赖,或是为它们设计建设对立的状态管理器。

    非常简略,轻量,和易于变更 。当一个Widget/ 模块 不在须要依据另一个 Widget/ 模块 的变更而更新时,只需移除其中的 接收器 即可,而不必为此而大改 依赖关系 或是 状态管理器

  • 简略、灵便、明确、易治理

    FBroadcast 为开发者提供了能够在任意时刻发送播送,和注册 / 移除接收器的能力,毫无束缚和灵便。

    播送和接收器之间通过明确的类型(字符串)来相互确认身份,指定类型的播送,只能被指定类型的接收器接管。

    FBroadcast 提供了环境注册反对,开发者能够在环境解构时,通过 [unregister()] 函数一次性移除环境中的所有类型接收器,而无需记忆和关怀到底须要移除那些接收器。例如,开发者能够在 Widgetdispose() 中,将注册在该 Widget 中的所有接收器一次性全副移除。

    借助古代 IDEA 的能力,开发者能够为广播系统建设一张(或多张)对立的播送类型索引表,通过 IDEA援用索引,开发者能够轻松的、高深莫测的看到该类型的播送在那些中央被发送过,在那些中央注册了接收器,非常易于治理和保护。而应用字符串来作为类型标识,使得开发者能够将不同类型的播送含意形容的足够清晰明确。

  • 粘性播送反对

    FBroadcast 提供了发送 粘性播送 的反对。在还没有注册任何接收器的状况下,开发者能够在事件产生时,事后发送一条 粘性播送 粘性播送 会被临时滞留在广播系统中,当有接收器被注册时,即会立刻播送。这有助于帮忙开发者在做逻辑设计时采取更清晰无效的思路。

    例如,当一个管制模块中的开关按钮被关上,而此时开关所管制的模块还没有被构建,就能够先发送一条 粘性播送 ,在模块被构建实现注册了接收器后,就会立刻接管到 粘性播送 而进入开启状态(这与相互依赖、定义对立状态治理或是参数传递,而后查看开关状态的思路有本质区别)。

???? 这真不是个别的 Broadcast

???? Base Broadcast

通信就要简略,明确

通过 FBroadcast 来注册,发送播送十分简便。

/// 注册接收器
/// 
/// register
FBroadcast.instance().register(Key_Message, (value, callback) {/// do something});

/// 发送音讯
/// 
/// send message
FBroadcast.instance().broadcast(Key_Message);

FBroadcast 容许开发者在发送音讯的时候,带有数据。

/// 注册接收器
/// 
/// register
FBroadcast.instance().register(Key_Message, (value, callback) {
  /// 获取数据
  /// 
  /// get data
  var data = value;
});

/// 发送音讯和数据
/// 
/// send message and data
FBroadcast.instance().broadcast(
  /// 音讯类型
  /// 
  /// message type
  Key_Message, 

  /// 数据
  /// 
  /// data
  value: data,
);

开发者能够抉择将特定类型的音讯进行长久化,这样就能轻易实现广播式的全局状态治理。

FBroadcast.instance().broadcast(
  /// 音讯类型
  /// 
  /// message type
  Key_Message, 

  /// 数据
  /// 
  /// data
  value: data, 

  /// 将音讯类型长久化
  /// 
  /// Persist the message types
  persistence: true,
);

当开发者将一个音讯类型长久化后,就能够在任意地位,通过 FBroadcast.value(String key) 来获取广播系统中该类型音讯的最新的数据。而更新广播系统中的数据只须要通过 broadcast() 即可实现。

⚠️留神,一个音讯类型一旦长久化就只能通过 FBroadcast.instance().clear(String key) 来从广播系统中移除该类型的音讯。

???? Sticky Broadcast

更多的抉择,构建更精美的利用

FBroadcast 反对开发者发送 粘性播送

FBroadcast.instance().stickyBroadcast(
  /// 音讯类型
  /// 
  /// message type
  Key_Message, 

  /// 数据
  /// 
  /// data
  value: data, 
);

当广播系统中没有对应类型的接收器时,粘性播送 将会临时滞留在零碎中,直到有该类型的接收器被注册,则会立刻收回播送(当广播系统中有对应类型的接收器时,就和一般播送具备雷同的体现)。

⛓ Two-way communication

双向沟通,双倍效率

FBroadcast 反对在播送发送点接管 接收器 返回的音讯。

/// 发送音讯
/// 
/// send message
FBroadcast.instance().broadcast(
  /// 音讯类型
  /// 
  /// message type
  Key_Message, 

  /// 数据
  /// 
  /// data
  value: data, 
  
  /// 接收器返回的音讯
  /// 
  /// The message returned by the receiver
  callback: (value){// do something}
);


/// 注册接收器
/// 
/// register
FBroadcast.instance().register(Key_Message, (value, callback) {
  /// 获取数据
  /// 
  /// get data
  var data = value;

  /// do something
  var result = logic();

  /// 返回音讯
  /// 
  /// return message
  callback(result);
});

通过 FBroadcast 可能给非常轻松的实现双向通信。

???? Bind Context

一码卸载,快捷精准

FBroadcast 反对在注册接收器时传入一个 环境对象(能够是任意类型),这会将接收器注册到环境中,当环境解构时,开发者能够不便的一次性移除所有在该环境中注册的接收器。

/// 注册接收器
/// 
/// register
FBroadcast.instance().register(
  /// 音讯类型
  /// 
  /// Message type
  Key_Message1,

  /// Receiver
  ///
  /// Receiver
  (value, callback) {/// do something},

  /// 更多接收器
  /// 
  /// more receiver
  more: {
    /// 音讯类型:接收器
    /// 
    /// Message type: Receiver
    Key_Message2: (value, callback) {/// do something},
    Key_Message3: (value, callback) {/// do something},
    Key_Message4: (value, callback) {/// do something},
  },

  /// 环境对象
  /// 
  /// context
  context: this,
);

/// 移除环境中的所有接收器
/// 
/// Remove all receivers from the environment
FBroadcast.instance().unregister(this);

???? 应用 FBroadcast 能够做些什么?

???? 消息传递

场景:点击 Start,Runner 开始 Run,显示屏须要实时更新运动员的状态。

???? 1. 创立 Runner:

/// Runner
class Runner {Runner() {
    /// register
    FBroadcast.instance().register(Key_RunnerState, (value, callback) {if (value is String && value.contains("Run")) {
        /// receive start run message
        FBroadcast.instance().broadcast(Key_RunnerState, value: "0m..");
        run(20);
      }
    });
  }

  run(double distance) {
    /// send running message
    Timer(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. 创立 UI:

Column(
  mainAxisAlignment: MainAxisAlignment.center,
  children: <Widget>[
    Stateful(
      /// init
      initState: (setState, data) {FBroadcast.instance().register(
          Key_RunnerState,
          (value, callback) {
            /// refresh ui
            setState(() {});
          },
          /// bind context
          context: data,
        );
      },
      builder: (context, setState, data) {
        return FSuper(
          ...
          /// get running message
          text: FBroadcast.value(Key_RunnerState) ?? "Preparing..",
        );
      },
    ),
    const SizedBox(height: 100),
    FButton(
      text: "Start"
      ...
      onPressed: () {
        /// send start run message
        FBroadcast.instance().broadcast(Key_RunnerState, value: "Running...");
      },
    ),
  ],
)

在下面的示例中,通过 FBroadcast 简略清晰的实现了 Runner 和 UI 之间的通信。

  1. 点击 Start 按钮,通过 FBroadcast 发送起跑音讯给 Runner;
  2. Runner 收到音讯后,开始 Run,同时一直通过 FBroadcast 收回 Running info;
  3. UI 因为注册了接收器,在接管到 Running info 时,通过 FBroadcast.value() 获取音讯数据,自动更新视图。

整个过程中,Runner 和 UI 之间是 齐全解耦 的,且 UI 只需在 init 注册接收器(receiver 中调用 setState((){})),就能依据音讯数据的变动,主动实时的更新视图,而无需开发者关怀整个过程。

⛓ 双向通信

场景:点击按钮申请定位,定位胜利后接管后果,刷新定位点

???? 1. 全局定位服务提供商

class LocationServer {LocationServer() {init();
  }

  init() {
    /// register Key_Location receiver
    FBroadcast.instance().register(Key_Location, (value, callback) async {var loc = await location();

      /// return message
      callback(loc);
    });
  }

  /// Analog positioning
  Future<List<double>> location() async {await Future.delayed(Duration(milliseconds: 2000));
    return [Random().nextDouble() * 280, Random().nextDouble() * 150];
  }
}

???? 2. 点击发送定位申请,接管返回音讯


FButton(
  ...
  text: "Location",
  onPressed: () {
    FLoading.show(context,
        color: Colors.black26, loading: buildLoading());
    /// request location
    FBroadcast.instance().broadcast(Key_Location,
        callback: (location) {
      /// The message returned by the receiver
      setState(() {FLoading.hide();
        this.location = location;
      });
    });
  },
)

FBroadcast 可能进一步简化须要双向通信的场景。开发者能够看到,在这个例子中,通过 FBroadcast 可能轻松的实现定位申请这种双向通信的场景,而且使得 定位服务提供商 UI实现的齐全的解耦。

UI 交互点 只须要发送定位申请的播送,任何注册该播送的 定位服务提供商 就能够接管该申请进行解决,而后返回后果到 UI 交互点。也就是说,随着我的项目的演进,开发者能够随时提供新的 定位服务提供商 ,而无需关怀任何的UI 变更。

???? 部分状态治理

场景:点击扭转 UI 色彩

???? 1. 点击收回事件

FButton(
  text: "Change Color",
  ...
  onPressed: () {
    /// send change color message
    FBroadcast.instance().broadcast(Key_Color, value: reduceColor());
  },
)

???? 2. UI 注册接收器

Stateful(
  /// init
  initState: (setState, data) {
    /// register
    FBroadcast.instance().register(
      Key_Color,
      (value, callback) {
        /// refresh ui
        setState(() {});
      },
      /// bind context
      context: data,
    );
  },
  builder: (context, setState, data) {
    return FSuper(
      ...
      /// get color value
      backgroundColor: FBroadcast.value<Color>(Key_Color) ?? mainBackgroundColor,
    );
  },
)

通过 FBroadcast 能够很轻易的实现 UI 交互之间的部分状态更新。下面的示例展现了色彩的变更,数据对象只有一个参数,理论开发过程中,开发者能够依据须要将通信的数据对象进行丰盛扩大。

开发者只须要在须要更新 UI 的 Widget 中 注册接收器,调用一次 setState((){}),在交互点收回音讯。而不必去被动的将触发逻辑和 setState((){}) 在所有的交互点都写一次。

???? 全局状态治理

场景:点击头像跳转登陆页,当账号密码不为 null 时,登陆按钮才能够点击。点击登陆按钮发送登陆申请,登陆胜利后,返回上一页,刷新用户信息。

???? 1. 用户信息 Widget 注册接收器

class Avatar extends StatefulWidget {
  @override
  _AvatarState createState() => _AvatarState();
}

class _AvatarState extends State<Avatar> {

  User user;
  int msgCount = 0;
  @override
  void initState() {super.initState();
    FBroadcast.instance().register(
      Key_MsgCount,
      /// register Key_MsgCount reviver
      (value, callback) => setState(() {msgCount = value;}),
      more: {
        /// register Key_User reviver
        Key_User: (value, callback) => setState(() {
          /// get value
          user = value;
        }),
      },
      /// bind context
      context: this,
    );
  }

  @override
  Widget 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())),
    );
  }

  @override
  void dispose() {super.dispose();
    /// remove all receivers from the environment
    FBroadcast.instance().unregister(this);
  }
}

登陆页中注册 Key_User 接收器,当接管到登陆音讯时,取出其中的数据,刷新 UI。

???? 2. 构建数据模型

class User{
  String name;
  String avatar;
  int messageCount = 0;
  String info;
}

???? 3. 构建逻辑解决对象

class LoginHandler {
  String _userName;
  String _password;

  /// set user name, check to see if login is allowed
  set 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 allowed
  set 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);
    }
  }

  /// login
  void login() {Timer(Duration(milliseconds: 1500), () {
      /// login success,send login success message —— Key_User
      FBroadcast.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_User
        persistence: true,
      );
    });
  }
}

将逻辑解决转移到 LoginHandler 中进行隔离,所有的处理结果都通过 FBroadcast 播送进来,使注册到广播系统中的对应接收器可能响应。

???? 4. 登陆页

class LoginPage extends StatefulWidget {
  @override
  _LoginPageState createState() => _LoginPageState();
}

class _LoginPageState extends State<LoginPage> {
  /// Logic handler
  LoginHandler handler = LoginHandler();
  /// input controller
  FSearchController _controller1 = FSearchController();
  FSearchController _controller2 = FSearchController();

  @override
  void initState() {super.initState();
    _controller1.setListener(() {
      /// update userName
      handler.userName = _controller1.text;
    });
    _controller2.setListener(() {
      /// update password
      handler.password = _controller2.text;
    });
  }

  @override
  Widget build(BuildContext context) {
    return {
      ...
      /// userName input
      FSearch(
        controller: _controller1,
        ...
      ),
      ...
      /// userName input
      FSearch(
        controller: _controller2,
        ...
      ),
      ...
      Stateful(initState: (setState, data) {
          /// register login receiver
          FBroadcast.instance().register(
            Key_Login,
            /// refresh ui
            (value, callback) => setState(() {}),
            more: {
              /// register user receiver
              Key_User: (value, callback) {FLoading.hide();
                Navigator.pop(context);
              },
            },
            /// bind context
            context: data,
          );
        }, 
        builder: (context, setState, data) {
          return FButton(
            ...
            text: "LOGIN",
            /// Key_Login value=true is allowed to click login
            onPressed: !(FBroadcast.value(Key_Login) ?? false)
                ? null
                : () {_controller1.clearFocus();
                    _controller2.clearFocus();
                    FLoading.show(context);
                    /// Execute login logic
                    handler.login();},
          );
      },),
      ...
    };
  }
}

注册接收器时,只需在接管回调中调用 setState((){}),后续所有的数据变动刷新,开发者就能够不必关注了。而给 UI 赋值能够不便的通过 FBroadcast.value() 获取对应数据来进行。

⚠️留神,对于须要全局应用的状态 / 数据模型,它们对应的播送类型,在发送时,须要至多有一次将 persistence 设置为 true。下面示例中,就在登陆胜利后,对 Key_User 类型的播送进行了长久化。

/// login success,send login success message —— Key_User
FBroadcast.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_User
  persistence: true,
);

下面的示例中展现了通过 FBroadcast 能够轻松疾速的实现 消息传递 ,进行 部分、全局状态治理和刷新 ,很好的将 各个模块,逻辑以及 UI 进行解耦。FBroadcast 提供了简洁易懂,而且非常灵便的广播系统,极少的解放让开发者能够疾速上手,轻松实现简单逻辑的简化,帮忙开发者构建出 易于保护的、简单的、精美的 利用。

FBroadcast 在应用过程中,配合对立的 播送类型注册表(也能够按模块分多张),开发者能够很轻易的借助 IDEA 的援用检索能力,随时查看所有播送的状况,对于一直迭代过程中的保护非常无益。

想要理解更多具体内容?请拜访 FBroadcast 官方主页 (PS:别忘了投出一个你认可的 Star 哦 ????)。

???? 如何应用?

在我的项目 pubspec.yaml 文件中增加依赖:

???? pub 依赖形式

dependencies:
  fbroadcast: ^< 版本号 >

⚠️ 留神,请到 pub 获取 FBroadcast 最新版本号

???? git 依赖形式

dependencies:
  fbroadcast:
    git:
      url: 'git@github.com:Fliggy-Mobile/fbroadcast.git'
      ref: '< 分支号 或 tag>'

⚠️ 留神,分支号 或 tag 请以 FBroadcast 官网我的项目为准。

感觉还不错?请到《FBroadcast》的 Github 主页投出您认可的一个 Star ???? 吧!

更多精彩组件

  • 《FSuper》- 帮忙开发者疾速构建精美的简单视图
  • 《FButton》- 为开发者筹备了诸多美好的配置项
  • 《FSwitch》- 具备低劣交互和视效的精美开关元素
  • 《FRadio》- 一个实用于简直任意单选场景的单选组件
  • 《FFloat》- 满足你对浮动元素的所有设想
  • 《FRefresh》- 轻松构建下拉刷新成果
  • 《FDottedLine》- 辉煌的虚线成果
  • 《FSearch》- 一应俱全的搜寻框组件
  • 《FToast》- 粗劣灵便的 Flutter 原生 Toast 组件
  • 《FLoading》- 帮忙开发者自在的应用 Loading
退出移动版