前言
- 首先,有很多的文章在说flutter bloc模式的利用,然而百分之八九十的文章都是在说,应用StreamController+StreamBuilder搭建bloc,晋升性能的会加上InheritedWidget,这些文章看了很多,真正写应用bloc作者开发的flutter_bloc却少之又少。没方法,只能去bloc的github下来找应用形式,最初去bloc官网翻文档。
- 各位靓仔,就不能好好说说flutter_bloc的应用吗?非要各种搬运bloc模式提出作者的那俩篇文章。当初,搞的杂家要本人去翻文档总结(手动滑稽)。
我的项目成果(倡议PC浏览器关上)
- Bloc范例成果
- Cubit范例成果
上面是Flutter_Bloc历程的一系列链接
- Flutter_Bloc起源
- Flutter_Bloc模式优化
- Flutter_Bloc诞生
- Flutter_Bloc官网文档
后面三个,是bloc作者写的bloc模式文档,典型的观察者模式的利用,最原始的就是java中CallBack模式。前俩篇文章就是咱们这些搬运工的次要“参考”的材料起源,这三篇文章在掘金上有翻译版,搜下bloc就能找到。最初一篇文章就是我次要总结演绎的源泉,作者在官网上写了好几个demo:计时器,登录,Todos,天气等等,大家能够本人去看看。
问题
首次应用flutter_bloc框架,可能会有几个疑难
- state外面定义了太多变量,某个事件只须要更新其中一个变量,其它的变量赋雷同值麻烦
- 进入某个模块,进行初始化操作:简单的逻辑运算,网络申请等,入口在哪定义
成果
- 好了,哔哔了一堆,看下咱们要用flutter_bloc实现的成果。
- 间接开Chrome演示,大家在虚拟机上跑也一样。
援用
- 先阐明下,bloc给的api很多,不同的api针对与解决场景不同,我要是把官网那些api全抄过也没啥意义;不,也有可能能够装币,我要是不阐明,大家说不定认为是我本人总结的呢!哈哈。
- OK,大家要是想晓得全场景的应用,能够去官网翻翻文档,我感觉学习一个模式或者框架的时候,最次要的是把主流程跑通,起码能够符合标准的堆页面,这样的话,就能够把这玩意用起来,再遇到想要的什么细节,就能够本人去翻文档,毕竟大体上曾经懂了,写过了几个页面,也有些领会,再去翻文档就很快能了解了。
库
flutter_bloc: ^6.0.6 #状态治理框架equatable: ^1.2.3 #加强组件相等性判断
- 看看flutter_bloc都推到6.0了,别再用StreamController手搭Bloc了!
插件
在Android Studio设置的Plugins里,搜寻:Bloc
装置重启下,就OK了
- 右击相应的文件夹,抉择“Bloc Class”,我在main文件夹新建的,填入的名字:main,就主动生成上面三个文件;:main_bloc,main_event,main_state;main_view是我本人新建,用来写页面的。
- 是不是感觉,还在手动新建这些bloc文件low爆了;就如同fish_redux,不必插件,让我手动去创立那六个文件,写那些模板代码,真的要原地爆炸。
Bloc范例
初始化代码
来看下这三个生成的bloc文件:main_bloc,main_event,main_state
main_bloc:这里就是咱们次要写逻辑的页面了
- mapEventToState办法只有一个参数,前面主动带了一个逗号,格式化代码就分三行了,倡议删掉逗号,格式化代码。
class MainBloc extends Bloc<MainEvent, MainState> { MainBloc() : super(MainInitial()); @override Stream<MainState> mapEventToState( MainEvent event, ) async* { // TODO: implement mapEventToState }}
- main_event:这里是执行的各类事件,有点相似fish_redux的action层
@immutableabstract class MainEvent {}
- main_state:状态数据放在这里保留,直达
@immutableabstract class MainState {}class MainInitial extends MainState {}
实现
阐明
- 这里对于简略的页面,state的应用形象状态继承实现的形式,未免有点麻烦,这里我进行一点小改变,state的实现类别有很多,官网写demo也有不必抽象类,间接class,相似实体类的形式开搞的。
- 老夫在代码关键点写上"///"类型正文,大家认真看看,拷进Android Studio外面,这些中央会变绿!大家好好领会下绿色代码!
main_bloc
- state变量是框架外部定义的,会默认保留上一次同步的MainSate对象的值
class MainBloc extends Bloc<MainEvent, MainState> { MainBloc() : super(MainState(selectedIndex: 0, isExtended: false)); @override Stream<MainState> mapEventToState(MainEvent event) async* { ///main_view中增加的事件,会在此处回调,此处解决完数据,将数据yield,BlocBuilder就会刷新组件 if (event is SwitchTabEvent) { ///获取到event事件传递过去的值,咱们拿到这值塞进MainState中 ///间接在state上扭转外部的值,而后yield,只能触发一次BlocBuilder,它外部会比拟上次MainState对象,如果雷同,就不build yield MainState() ..selectedIndex = event.selectedIndex ..isExtended = state.isExtended; } else if (event is IsExtendEvent) { yield MainState() ..selectedIndex = state.selectedIndex ..isExtended = !state.isExtended; } }}
- main_event:在这里就能看见,view触发了那些事件了;保护起来也很爽,看看这里,也很快能懂页面在干嘛了
@immutableabstract class MainEvent extends Equatable{ const MainEvent();}///切换NavigationRail的tabclass SwitchTabEvent extends MainEvent{ final int selectedIndex; const SwitchTabEvent({@required this.selectedIndex}); @override List<Object> get props => [selectedIndex];}///开展NavigationRail,这个逻辑比较简单,就不必传参数了class IsExtendEvent extends MainEvent{ const IsExtendEvent(); @override List<Object> get props => [];}
main_state:state有很多种写法,在bloc官网文档上,不同我的项目state的写法也很多
- 这边变量名能够设置为私用,用get和set可选择性的设置读写权限,因为我这边设置的俩个变量全是必用的,读写均要,就设置私有类型,不必下划线“_”去标记公有了。
- 对于生成的模板代码,咱们在这:去掉@immutable注解,去掉abstract;
- 这里说下加上@immutable和abstract的作用,这边是为了标定不同状态,这种写法,会使得代码变得更加麻烦,用state不同状态去标定业务事件,代价太大,这边用一个变量去标定,很容易轻松代替
class MainState{ int selectedIndex; bool isExtended; MainState({this.selectedIndex, this.isExtended});}
main_view
- 这边就是咱们的界面层了,很简略,将须要刷新的组件,用BlocBuilder包裹起来,应用BlocBuilder:提供的state去赋值就ok了,context去增加执行的事件,context用StatelessWidget中提供的或者BlocBuilder提供的都行
void main() { runApp(MyApp());}class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return MaterialApp( home: MainPage(), ); }}class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { ///创立BlocProvider的,表明该Page,咱们是用MainBloc,MainBloc是属于该页面的Bloc了 return BlocProvider( create: (BuildContext context) => MainBloc(), child: BodyPage(), ); }}class BodyPage extends StatelessWidget { @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: Text('Bloc')), body: totalPage(), ); }}Widget totalPage() { return Row( children: [ navigationRailSide(), Expanded(child: Center( child: BlocBuilder<MainBloc, MainState>(builder: (context, state) { ///看这看这:刷新组件! return Text("selectedIndex:" + state.selectedIndex.toString()); }), )) ], );}//减少NavigationRail组件为侧边栏Widget navigationRailSide() { //顶部widget Widget topWidget = Center( child: Padding( padding: const EdgeInsets.all(8.0), child: Container( width: 80, height: 80, decoration: BoxDecoration( shape: BoxShape.circle, image: DecorationImage( image: NetworkImage("https://ss2.bdstatic.com/70cFvnSh_Q1YnxGkpoWK1HF6hhy/it/u=3383029432,2292503864&fm=26&gp=0.jpg"), fit: BoxFit.fill), )), ), ); //底部widget Widget bottomWidget = Container( child: BlocBuilder<MainBloc, MainState>( builder: (context, state) { return FloatingActionButton( onPressed: () { ///增加NavigationRail开展,膨胀事件 context.bloc<MainBloc>().add(IsExtendEvent()); }, ///看这看这:刷新组件! child: Icon(state.isExtended ? Icons.send : Icons.navigation), ); }, ), ); return BlocBuilder<MainBloc, MainState>(builder: (context, state) { return NavigationRail( backgroundColor: Colors.white12, elevation: 3, ///看这看这:刷新组件! extended: state.isExtended, labelType: state.isExtended ? NavigationRailLabelType.none : NavigationRailLabelType.selected, //侧边栏中的item destinations: [ NavigationRailDestination( icon: Icon(Icons.add_to_queue), selectedIcon: Icon(Icons.add_to_photos), label: Text("测试一")), NavigationRailDestination( icon: Icon(Icons.add_circle_outline), selectedIcon: Icon(Icons.add_circle), label: Text("测试二")), NavigationRailDestination( icon: Icon(Icons.bubble_chart), selectedIcon: Icon(Icons.broken_image), label: Text("测试三")), ], //顶部widget leading: topWidget, //底部widget trailing: bottomWidget, selectedIndex: state.selectedIndex, onDestinationSelected: (int index) { ///增加切换tab事件 context.bloc<MainBloc>().add(SwitchTabEvent(selectedIndex: index)); }, ); });}
Bloc范例优化
反思
从下面的代码来看,理论存在几个隐式问题,这些问题,刚开始应用时候,没异样的感觉,然而应用bloc久了后,感觉必定越来越强烈
state问题
- 初始化问题:这边初始化是在bloc里,间接在构造方法外面赋初值的,state中一旦变量多了,还是这么写,会感觉极其好受,不好治理。须要优化
- 能够看见这边咱们只改变selectedIndex或者isExtended;另一个变量不须要变动,须要放弃上一次的数据,进行了此类:state.selectedIndex或者state.isExtended赋值,一旦变量达到十几个乃至几十个,还是如此写,是让人极其解体的。须要优化
bloc问题
- 如果进行一个页面,须要进行简单的运算或者申请接口后,能力通晓数据,进行赋值,这里必定须要一个初始化入口,初始化入口须要怎么去定义呢?
优化实现
这边残缺走一下流程,让大家能有个残缺的思路
state:首先来看看咱们对state中的优化,这边进行了俩个很重要优化,减少俩个办法:init()和clone()
- init():这里初始化对立用init()办法去治理
- clone():这边克隆办法,是十分重要的,一旦变量达到俩位数以上,就能粗浅领会该办法是如许的重要
class MainState { int selectedIndex; bool isExtended; ///初始化办法,根底变量也须要赋初值,不然会报空异样 MainState init() { return MainState() ..selectedIndex = 0 ..isExtended = false; } ///clone办法,此办法实现参考fish_redux的clone办法 ///也是对官网Flutter Login Tutorial这个demo中copyWith办法的一个优化 ///Flutter Login Tutorial(https://bloclibrary.dev/#/flutterlogintutorial) MainState clone() { return MainState() ..selectedIndex = selectedIndex ..isExtended = isExtended; }}
event
- 这边定义一个MainInit()初始化办法,同时去掉Equatable继承,在我目前的应用中,感觉它用途不大。。。
@immutableabstract class MainEvent {}///初始化事件,这边目前不须要传什么值class MainInitEvent extends MainEvent {}///切换NavigationRail的tabclass SwitchTabEvent extends MainEvent { final int selectedIndex; SwitchTabEvent({@required this.selectedIndex});}///开展NavigationRail,这个逻辑比较简单,就不必传参数了class IsExtendEvent extends MainEvent {}
bloc
- 这减少了初始化办法,请留神,如果须要进行异步申请,同时须要将相干逻辑提炼一个办法,咱们在这里配套Future和await就能解决在异步场景下同步数据问题
- 这里应用了克隆办法,能够发现,咱们只有关注本人须要扭转的变量就行了,其它的变量都在外部赋值好了,咱们不须要去关注;这就大大的便捷了页面中有很多变量,只须要变动一俩个变量的场景
- 留神:如果变量的数据未扭转,界面相干的widget是不会重绘的;只会重绘变量被扭转的widget
class MainBloc extends Bloc<MainEvent, MainState> { MainBloc() : super(MainState().init()); @override Stream<MainState> mapEventToState(MainEvent event) async* { ///main_view中增加的事件,会在此处回调,此处解决完数据,将数据yield,BlocBuilder就会刷新组件 if (event is MainInitEvent) { yield await init(); } else if (event is SwitchTabEvent) { ///获取到event事件传递过去的值,咱们拿到这值塞进MainState中 ///间接在state上扭转外部的值,而后yield,只能触发一次BlocBuilder,它外部会比拟上次MainState对象,如果雷同,就不build yield switchTap(event); } else if (event is IsExtendEvent) { yield isExtend(); } } ///初始化操作,在网络申请的状况下,须要应用如此办法同步数据 Future<MainState> init() async { return state.clone(); } ///切换tab MainState switchTap(SwitchTabEvent event) { return state.clone()..selectedIndex = event.selectedIndex; } ///是否开展 MainState isExtend() { return state.clone()..isExtended = !state.isExtended; }}
view
- view层代码太多,这边只减少了个初始化事件,就不从新把全副代码贴出来了,初始化操作间接在创立的时候,在XxxBloc上应用add()办法就行了,就能起到进入页面,初始化一次的成果;add()办法也是Bloc类中提供的,遍历事件的时候,就顺便查看了add()这个办法是否增加了事件;阐明,这是框架顺便提供了一个初始化的办法
这个初始化形式是在官网示例找到的
- 我的项目名:Flutter Infinite List Tutorial
- 我的项目地址:flutter-infinite-list-tutorial
class MainPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( ///在MainBloc上应用add办法,增加初始化事件 create: (BuildContext context) => MainBloc()..add(MainInitEvent()), child: BodyPage(), ); }}///下方其余代码省略...........
搞定
- OK,通过这样的优化,解决了几个痛点。理论在view中重复是要用BlocBuilder去更新view,写起来有点麻烦,这里咱们能够写一个,将其中state和context变量,往提出来的Widget办法传值,也是蛮不错的
- 大家放弃观察者模式的思维就行了;观察者(回调刷新控件)和被观察者(产生相应事件,增加事件,去告诉观察者),bloc层是处于观察者和被观察者两头的一层,咱们能够在bloc外面搞业务,搞逻辑,搞网络申请,不能搞基;拿到Event事件传递过去的数据,把解决好的、符合要求的数据返回给view层的观察者就行了。
- 应用框架,不拘泥框架,在观察者模式的思维上,灵便的去应用flutter_bloc提供Api,这样能够大大的缩短咱们的开发工夫!
Cubit范例
- Cubit是Bloc模式的一种简化版,去掉了event这一层,对于简略的页面,用Cubit来实现,开发体验是大大的好啊,上面介绍下该种模式的写法
创立
- 首先创立Cubit一组文件,抉择“Cubit Class”,点击,新建名称填写:Counter
新建好后,他会生成俩个文件:counter_cubit,counter_state,来看下生成的代码
原始生成代码
- counter_cubit
class CounterCubit extends Cubit<CounterState> { CounterCubit() : super(CounterInitial());}
- counter_state
@immutableabstract class CounterState {}class CounterInitial extends CounterState {}
依照生成的这种state形式去写,比拟麻烦,这边调整下
调整后代码
- counter_cubit
class CounterCubit extends Cubit<CounterState> { CounterCubit() : super(CounterState().init());}
- counter_state
class CounterState { ///初始化办法 CounterState init() { return CounterState(); } ///克隆办法,针对于刷新界面数据 CounterState clone() { return CounterState(); }}
OK,这样调整了下,上面写起来就会难受很多,也会很省事
实现计时器
- 来实现下一个灰常简略的计数器
成果
- 来看下实现成果吧,这边不上图了,大家点击上面的链接,能够间接体验Cubit模式写的计时器
- 实现成果:点我体验实际效果
实现
实现很简略,三个文件就搞定,看下流程:state -> cubit -> view
- state:这个很简略,加个计时变量
class CounterState { int count; CounterState init() { return CounterState()..count = 0; } CounterState clone() { return CounterState()..count = count; }}
cubit
- 这边加了个自增办法:increase()
- event层理论是所有行为的一种整合,不便对逻辑过于简单的页面,所有行为的一种保护;然而过于简略的页面,就那么几个事件,还独自保护,就没什么必要了
- 在cubit层写的公共办法,在view外面能间接调用,更新数据应用:emit()
- cubit层应该能够算是:bloc层和event层一种联合后的简写
class CounterCubit extends Cubit<CounterState> { CounterCubit() : super(CounterState().init()); ///自增 void increase() => emit(state.clone()..count = ++state.count);}
view
- view层的代码就非常简单了,点击办法外面调用cubit层的自增办法就ok了
class CounterPage extends StatelessWidget { @override Widget build(BuildContext context) { return BlocProvider( create: (BuildContext context) => CounterCubit(), child: BlocBuilder<CounterCubit, CounterState>(builder: _counter), ); } Widget _counter(BuildContext context, CounterState state) { return Scaffold( appBar: AppBar(title: const Text('Cubit范例')), body: Center( child: Text('点击了 ${state.count} 次', style: TextStyle(fontSize: 30.0)), ), floatingActionButton: FloatingActionButton( onPressed: () => context.bloc<CounterCubit>().increase(), child: const Icon(Icons.add), ), ); }}
总结
在Bloc模式外面,如果页面不是过于简单,应用Cubit去写,根本齐全够用了;然而如果业务过于简单,还是须要用Bloc去写,须要将所有的事件行为治理起来,便于前期保护
OK,Bloc的简化模块,Cubit模式就这样讲完了,对于本人业务写的小我的项目,我就常常用这个Cubit去写
最初
- Bloc还有很多Api针对不同的场景十分的实用,例如:MultiBlocProvider,BlocListener,MultiBlocListener,BlocConsumer等等,这外面有些Api和Provider的Api是十分类似的,例如MultiXxxxx,这都是为了缩小嵌套,提供多个全局Bloc而提供,大家能够去瞧瞧看,用法也都十分的类似
Cubit范例代码地址
- Cubit范例代码
Bloc范例代码地址
- Bloc范例代码
flutter_bloc相干Api白嫖地址
- flutter_bloc相干Api
flutter_bloc
- GitHub:https://github.com/felangel/bloc
- Pub:https://pub.dev/packages/flutter_bloc