BLoC(Business Logic Component)设计模式是一个新鲜词汇, 在2018年1月的DartConf上被提出,其实是在Rx系列思想之下发展出来的响应式编程模式。
PS: 有时候从后端研发的角度来看,前端领域的一些想法确实挺前卫的。
其核心在于:将变量的变化看作一种流,并把Widget的状态绑定在流上,从而当变量改变时候,Widget接收到事件并作出响应的改变。因此BLoC可以做到完全将业务逻辑封装起来,Widget仅通过输入、输出和逻辑模块交互,做到完全的解耦。
BLoC的结构盗图一下:
这里的实现 90% 出自这位知乎大佬的文章,各位可以去围观一下:
在 Flutter 中使用 Bloc 来处理数据并更新 UI
可以把Stream看作管道,事件从一端输入,监听方从另一端获取事件并作出相应的改变。这里我们的Widget需要使用到StreamBuilder。
在Flutter中 StreamController 管理流, controller.sink 是入口, controller.stream 是出口
假设我们需要一个环境变量代表开和关,而App有多个页面的Switch开关控件与这个变量进行关联,我需要在任何页面拨动开关的时候,其它页面对应的开关的状态保持一致。
这里我们可以设想到的输入事件有两种:
因此会有两个Stream: 值的Stream,和用户操作事件Stream
class AppSwitchBLoC {
bool _switchValue = false;
StreamController<bool> _appSwitchController = StreamControler();
// 用于值改变事件的输入(私有变量,不暴露出来)
StreamSink<bool> get _inSwitch = _appSwitchController.sink;
// 用于值改变事件的输入(值的输出暴露出来)
Stream<bool> get outSwitchValue = _appSwitchController.stream;
StreamController _switchActionController = StreamController();
// 用户输入触发(暴露出来)
StreamSink get changeSwitchValue = _switchActionController.sink;
AppSwitchBLoC() {
// 指定_handleEvent函数处理用户的输入事件
_switchActionController.stream.listen(_handleEvent);
}
_handleEvent(data) {
// 业务处理逻辑,这里就是将_switchValue的值变为用户输入的值
_switchValue = data;
// 将用户输入的值传递给值Stream, 从而触发界面元素Widget改变
_inSwitch.add(_switchValue);
}
}
此代码完全来源于上面提到的知乎大佬,各位自行领悟代码…
abstract class BlocBase{
void dispose();
}
class BlocProvider<T extends BlocBase> extends StatefulWidget{
final T bloc;
final Widget child;
BlocProvider({
Key key,
@required this.child,
@required this.bloc,
}) : super(key: key);
@override
_BlocProviderState<T> createState() => new _BlocProviderState<T>();
static Type _typeOf<T>() => T;
static T of<T extends BlocBase>(BuildContext context){
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
return provider.bloc;
}
}
class _BlocProviderState<T> extends State<BlocProvider<BlocBase>>{
@override
void dispose(){
widget.bloc.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return widget.child;
}
}
在此基础上,上边的AppSwitchBLoC变为:
class AppSwitchBLoC implements BlocBase {
/// 源代码不变
// 添加 dispose 方法
@override
void dispose() {
// 释放stream资源
_appSwitchController.close();
_switchActionController.close();
}
}
借助上边的Provider即可
首先进入AppSwitch页面时,原先仅需要进入Page实例即可,这里需要用Provider封装一层。
/// 改造前
Navigator.of(context).push(new MaterialPageRoute(builder: (context){
return AppSwitchPage();
}));
/// 改造后
Navigator.of(context).push(new MaterialPageRoute(builder: (context){
return BlocProvider<AppSwitchPage>(
bloc: AppSwitchBLoC(),
child: AppSwitchPage()
);
}));
此时在AppSwitchPage页面即可获取BLoC实例
class AppSwitchPage extends StatefulWidget {
@override
_AppSwitchPageState createState() => _AppSwitchPageState();
}
class _AppSwitchPageState extends State<AppSwitchPage> {
@override
Widget build(BuildContext context) {
final AppSwitchBLoC bloc = BlocProvider.of<AppSwitchBLoC>(context);
return Container(
child: StreamBuilder<bool> (
stream: bloc.outSwitchValue,
initialData: false,
builder: (context, AsyncSnapshot<bool> snapshot) {
return Switch(
onChange: (value) {
// 这里输入用户操作后的开关值
bloc.changeSwitchValue.add(value);
},
// 这里从流里监听值的变化
value: snapshot.data,
);
}
)
);
}
}