文章结构:web
widget只作UI展现,bloc实现控制逻辑,model作数据封装。 我在运营线的新需求开发中实际使用了bloc作了一个新的页面,就是商机败北页面。 这个页面有三级的败北缘由选择,败北缘由是级联的,还有每次选择设备后要从新请求库存,提交败北等操做。ajax
在同一个页面中跨widget数据获取数据和操做比较多,例如提交败北的时候,须要获取多个子widget中的数值,若是没有使用bloc的话,就须要在子widget的构造方法中传递model或回调方法。数组
若是页面层级较多,这种model和回调方法就须要逐级传递。 例如在下图左边,须要把submiModel或回调方法从最上层一直传递到最底部,才能作到收集全部的子widget中的信息,最终集合全部的widget中的参数到submitModel中,提交败北请求。缓存
下面右边的代码就是这样逐层传递参数的方法,若是层级不少的话会很是头疼,不只要重复的声明变量,并且若是修改的话要每一个地方都改。bash
而使用了bloc的话,构造函数就特别干净,不须要逐层传递model或者回调方法。服务器
下面的两块代码都是页面widget结构中最下层的两个widget,不须要在构造方法中传递任何参数或回调方法,可是一样能调用拿到model和调用回调方法。怎么实现的继续往下看。:)网络
以前在项目中直接使用setState会产生一些问题,例如当前State是unmounted状态,这时直接调用setState会抛异常。异步
而使用bloc的话刷新页面是使用以下方式。关键的代码是_requestController.add ide
咱们现有项目中有一些很是复杂的页面,可是只使用了一个文件,全部的业务逻辑和widget组件都在这个页面里面。函数
下面是一段这种混杂网络请求、widget显示的代码示例。
这样相比于多个子widget的方式就不用逐层传递数据和回调方法。可是破坏了面向对象开发的基本原则:信息隐藏,好比修改某个widget的一个小功能,由于在一个很大的文件中修改,其中业务逻辑错综复杂,改一个小功能可能就会影响其余逻辑,产生意料不到的后果,这一块的代码就变得很是难以维护。
同时直接调用setState刷新整个页面的性能也有问题,也会产生抛异常的问题。
而使用bloc的方式会更加自由,能够像第二点中的图中同样使用多个子widget,在子widget中进行刷新。
也能够直接改造上图的代码,在一个widget中实现局部刷新。
第四个优势其实和第三个是一并实现的,都是经过streamBuidler和streamController,这二者是配套使用的。其实bloc就是streamBuilder+streamController+ancestorWidgetOfExactType,和web端的ajax比较相似,几项现有技术整合出了新的东西。
StreamBuilder要传入一个stream对象才能实现刷新,而这个stream要从streamController中获取的。
当在上面的代码中调用_requestController.add(deviceModel)后,下面的StreamBuilder就会自动调用builder方法,实现局部刷新StreamBuilder,而不是刷新外层的InkWell。
StreamBuilder是一个Widget,能够在任意地方插入,实现局部刷新很是简单。
要实现一个BlocProvider,现有项目中已经集成,整个项目只须要一个BlocProvider,能够放在Utils目录中。
实现Bloc其实就只用这一个类和flutter自带的方法就好了,代码很是简单,并不须要在yaml文件中引入第三方库。
全部的bloc都须要继承BlocBase,一般一个bloc对应一个会刷新页面的业务逻辑(如网络请求、切换tab)。
下面是bloc建立和blocProvider的代码。
bloc触发刷新的方式就是使用StreamBuilder+StreamController,可是直接使用StreamController有一个很大的缺点就是只能进行一对一的listen,且模式很是单一。因此须要使用到rxdart,下面会讲到。
bloc另外一重要特性就是跨层级获取bloc,在bloc能够获取到model或者调用方法。
其中使用了ancestorWidgetOfExactType,由于flutter widget是树状结构的,结构层次保存在BuildContext中,能够从子节点依次往父节点找符合类型的widget,因此全部使用了BlocProvider包裹的widget均可以经过of方法找到,再返回widget中的bloc。(bloc最终是保存在外一层的state中)。
final type = _typeOf<BlocProvider<T>>();
BlocProvider<T> provider = context.ancestorWidgetOfExactType(type);
复制代码
在下图中,最下层的widget调用of方法后,能够直接获取到顶层页面的submitBloc,由于flutter widget是树状结构的,结构层次保存在BuildContext中,能够从子节点依次往父节点找符合类型的widget,因此全部使用了BlocProvider包裹的widget均可以经过of方法找到,再返回widget中的bloc。(bloc最终是保存在外一层的state中)。
StreamController实现了观察者模式,监听者不是直接被调用,而是处于观察状态,当event加入streamController后,监听者得到异步回调。
var obs = Observable(Stream.fromIterable([1,2,3,4,5]))
.map((item)=>++item);
obs.listen(print);
输出:2 3 4 5 6
复制代码
Subject是对StreamController的扩展,经常使用的有如下几种。
PublishSubject:StreamController广播版,streamController只能有一个listener,PublishSubject能够屡次listen。下面的其余几种Subject也都是广播版。
BehaviorSubject: 缓存最近一次的事件。若是先发生event,后listen,也能收到缓存的event。
ReplaySubject: 缓存全部的事件,以前加入的全部event,listen后,都会顺序发送过来。
发现这是因为每次都建立新的bloc致使的,经过使用一个bloc list解决了这个问题。
static ZNImageBLocList _getInstance() {
if (_instance == null) {
_instance = new ZNImageBLocList._internal();
}
return _instance;
}
List<ZNImageBloc> blocList;
ZNImageBloc getBloc(String url,ZNImageConfig config){
for(ZNImageBloc bloc in blocList){
if(bloc.imageUrl==url){
return bloc;
}
}
ZNImageBloc bloc = new ZNImageBloc(url, config);
blocList.add(bloc);
return bloc;
}
复制代码
class ZnWebImageState extends State<ZnWebImage> {
ZNImageBloc bloc;
@override
void dispose() {
// TODO: implement dispose
bloc.dispose();
super.dispose();
}
@override
void initState() {
// TODO: implement initState
super.initState();
bloc = ZNImageBLocList.instance.getBloc(widget.url, widget.config);
}
@override
Widget build(BuildContext context) {
// TODO: implement build
return BlocProvider<ZNImageBloc>(
bloc: bloc,
child: ZNImageContainer(),
);
}
}
复制代码
这样多个zn_web_image的widget就能共用同一个bloc,在下载过程当中同步显示进度条,下载完成后同时收到通知显示下载好的图片。
bloc在征信项目中也解决了一个很头疼的问题,就是否经过认证的状态,这个状态全局共用,且有多个变动入口,认证状态变动后要求全部页面更新显示。
1.第一个tab中的首页顶部。
2.确认订单页面,能够从第一个tab首页进入,也能够从第二个tab的订单列表进入。
3.第四个tab的我的中心页面。
最开始没有使用bloc的时候,写了多个网络请求获取认证状态,isAuth属性也在每一个页面分别保存。
1.首页经过requestAuth从服务器端获取isAuth属性。
2.确认订单页面也须要从requestAuth获取isAuth属性。虽然首页经过请求获取到了isAuth属性,从首页进入确认订单页面能够传递isAuth属性。可是从订单列表进入时没有这个属性,订单列表是和首页同时初始化的,仍是须要网络请求。
3.我的中心页面也须要从requestAuth获取isAuth属性。虽然首页经过请求获取到了isAuth属性,可是我的中心页面是和首页同时在tabbarView中初始化的,不能经过构造方法传递isAuth属性。
这样很是的麻烦,并且工做量大不少。
使用bloc以后,isAuth这样全局共用的属性,只须要在MaterialApp外面加一层BlocProvider就好了,任何一个子页面都能直接获取isAuth,而且能同步isAuth的状态更新,刷新UI显示。
//我的认证bloc
CreditAuthBloc authBloc;
@override
void dispose() {
authBloc.dispose();
super.dispose();
}
@override
void initState() {
super.initState();
authBloc = CreditAuthBloc();
}
@override
Widget build(BuildContext context) {
return BlocProvider<CreditAuthBloc>(bloc: authBloc,child: MaterialApp(
title: '',
initialRoute:'/',
home: Scaffold(
body: Column(
children: <Widget>[
Expanded(
child: TabBarView(physics: NeverScrollableScrollPhysics(),controller: tabController, children: [
ZNMarketPage(),//首页
ZNOrderList(),//订单列表
ZNBill(),
ZNMyCenter(),//我的中心
]),
),
],
)),
),);
}
复制代码
子页面获取bloc
//使用bloc中的auth
// int userAuthStatus = 0; //我的认证 0: 未认证, 1: 已认证
CreditAuthBloc authBloc;
@override
void didChangeDependencies() {
// TODO: implement didChangeDependencies
super.didChangeDependencies();
authBloc = BlocProvider.of<CreditAuthBloc>(context);
}
复制代码
经过streamBuilder使用bloc