flutter_bloc 使用将从下图的三个维度说明
前言
首先,有很多的文章在说 flutter bloc 模式的应用,但是百分之八九十的文章都是在说,使用 StreamController+StreamBuilder 搭建 bloc,提升性能的会加上 InheritedWidget,这些文章看了很多,真正写使用 bloc 作者开发的 flutter_bloc 却少之又少。没办法,只能去 bloc 的 github 上去找使用方式,最后去 bloc 官网翻文档。
蛋痛,各位叼毛,就不能好好说说 flutter_bloc 的使用吗?非要各种抄 bloc 模式提出作者的那俩篇文章。现在,搞的杂家这个伸手党要自己去翻文档总结(手动滑稽)。
项目效果(建议 PC 浏览器打开)
下面是 Flutter_Bloc 历程的一系列链接
问题
初次使用 flutter_bloc 框架,可能会有几个疑问
准备工作
说明
引用
库
flutter_bloc: ^6.1.1 #状态管理框架
equatable: ^1.2.3 #增强组件相等性判断
复制代码
插件
在 Android Studio 设置的 Plugins 里,搜索:Bloc
安装重启下,就 OK 了
Bloc 范例
效果
初始化代码
来看下这三个生成的 bloc 文件:main_bloc,main_event,main_state
class MainBloc extends Bloc<MainEvent, MainState> {
MainBloc() : super(MainInitial());
@override
Stream<MainState> mapEventToState(
MainEvent event,
) async* {
// TODO: implement mapEventToState
}
}
复制代码
@immutable
abstract class MainEvent {}
复制代码
@immutable
abstract class MainState {}
class MainInitial extends MainState {}
复制代码
实现
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
);
}
}
复制代码
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;
}
}
}
复制代码
@immutable
abstract class MainEvent extends Equatable{
const MainEvent();
}
///切换NavigationRail的tab
class 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});
}
复制代码
class MainPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return _buildBg(children: [
//侧边栏
_buildLeftNavigation(),
//右边主体内容
Expanded(child: Center(
child: BlocBuilder<MainBloc, MainState>(builder: (context, state) {
return Text(
"选择Index:" + state.selectedIndex.toString(),
style: TextStyle(fontSize: 30.0),
);
}),
))
]);
}
Widget _buildBg({List<Widget> children}) {
///创建BlocProvider的,表明该Page,我们是用MainBloc,MainBloc是属于该页面的Bloc了
return BlocProvider(
create: (BuildContext context) => MainBloc(),
child: Scaffold(
appBar: AppBar(title: Text('Bloc')),
body: Row(children: children),
),
);
}
//增加NavigationRail组件为侧边栏
Widget _buildLeftNavigation() {
return BlocBuilder<MainBloc, MainState>(builder: (context, state) {
return NavigationRail(
backgroundColor: Colors.white,
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: _buildNavigationTop(),
//底部widget
trailing: _buildNavigationBottom(),
selectedIndex: state.selectedIndex,
onDestinationSelected: (int index) {
///添加切换tab事件
BlocProvider.of<MainBloc>(context)
.add(SwitchTabEvent(selectedIndex: index));
},
);
});
}
Widget _buildNavigationTop() {
return 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 _buildNavigationBottom() {
return Container(
child: BlocBuilder<MainBloc, MainState>(
builder: (context, state) {
return FloatingActionButton(
onPressed: () {
///添加NavigationRail展开,收缩事件
BlocProvider.of<MainBloc>(context).add(IsExtendEvent());
},
child: Icon(state.isExtended ? Icons.send : Icons.navigation),
);
},
),
);
}
}
复制代码
Bloc 范例优化
反思
从上面的代码来看,实际存在几个隐式问题,这些问题,刚开始使用时候,没异常的感觉,但是使用 bloc 久了后,感觉肯定越来越强烈
state 问题
初始化问题:这边初始化是在 bloc 里,直接在构造方法里面赋初值的,state 中一旦变量多了,还是这么写,会感觉极其难受,不好管理。需要优化
可以看见这边我们只改动 selectedIndex 或者 isExtended;另一个变量不需要变动,需要保持上一次的数据,进行了此类:state.selectedIndex 或者 state.isExtended 赋值,一旦变量达到十几个乃至几十个,还是如此写,是让人极其崩溃的。需要优化
bloc 问题
如果进行一个页面,需要进行复杂的运算或者请求接口后,才能知晓数据,进行赋值,这里肯定需要一个初始化入口,初始化入口需要怎样去定义呢?
插件
因为官方插件生成的写法,和调整后写法差距有点大,而且官方插件不支持生成 view 层和相关设置,此处我就撸了一个插件,完善了相关功能
请注意,wrap 代码和提示代码片段,参靠了官方插件规则
Wrap Widget 规则来着:intellij_generator_plugin
快捷代码生成规则来着: intellij_generator_plugin
优化实现
这边完整走一下流程,让大家能有个完整的思路
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;
}
}
复制代码
@immutable
abstract class MainEvent {}
///初始化事件,这边目前不需要传什么值
class MainInitEvent extends MainEvent {}
///切换NavigationRail的tab
class 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 {
...
Widget _buildBg({List<Widget> children}) {
///创建BlocProvider的,表明该Page,我们是用MainBloc,MainBloc是属于该页面的Bloc了
return BlocProvider(
create: (BuildContext context) => MainBloc()..add(MainInitEvent()),
child: Scaffold(
appBar: AppBar(title: Text('Bloc')),
body: Row(children: children),
),
);
}
///下方其余代码省略...........
}
复制代码
搞定
OK,经过这样的优化,解决了几个痛点。实际在 view 中反复是要用 BlocBuilder 去更新 view,写起来有点麻烦,这里我们可以写一个,将其中 state 和 context 变量,往提出来的 Widget 方法传值,也是蛮不错的
大家保持观察者模式的思想就行了;观察者(回调刷新控件)和被观察者(产生相应事件,添加事件,去通知观察者),bloc 层是处于观察者和被观察者中间的一层,我们可以在 bloc 里面搞业务,搞逻辑,搞网络请求,不能搞基;拿到 Event 事件传递过来的数据,把处理好的、符合要求的数据返回给 view 层的观察者就行了。
使用框架,不拘泥框架,在观察者模式的思想上,灵活的去使用 flutter_bloc 提供 Api,这样可以大大的缩短我们的开发时间!
Cubit 范例
创建
新建好后,他会生成三个文件:cubit,state,view;来看下生成的代码
模板代码
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState().init());
}
复制代码
class CounterState {
CounterState init() {
return CounterState();
}
CounterState clone() {
return CounterState();
}
}
复制代码
class CounterPage extends StatelessWidget {
final cubit = CounterCubit();
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => cubit,
child: Container(),
);
}
}
复制代码
实现计时器
效果
实现
实现很简单,三个文件就搞定,看下流程:state -> cubit -> view
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);
}
复制代码
class CounterPage extends StatelessWidget {
final cubit = CounterCubit();
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => cubit,
child: Scaffold(
appBar: AppBar(title: const Text('Cubit范例')),
body: Center(
child: BlocBuilder<CounterCubit, CounterState>(
builder: (context, state) {
return Text(
'点击了 ${state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => cubit.increase(),
child: const Icon(Icons.add),
),
),
);
}
}
复制代码
总结
在 Bloc 模式里面,如果页面不是过于复杂,使用 Cubit 去写,基本完全够用了;但是如果业务过于复杂,还是需要用 Bloc 去写,需要将所有的事件行为管理起来,便于后期维护
OK,Bloc 的简化模块,Cubit 模式就这样讲完了,对于自己业务写的小项目,我就经常用这个 Cubit 去写
全局 Bloc
说明
什么是全局 Bloc?
BlocProvider 介绍里面有这样的形容:BlocProvider should be used to create new blocs which will be made available to the rest of the subtree(BlocProvider 应该被用于创建新的 Bloc,这些 Bloc 将可用于其子树)
这样的话,我们只需要在主入口地方使用 BlocProvider 创建 Bloc,就能使用全局的 XxxBloc 了,这里的全局 XxxBloc,state 状态都会被保存的,除非关闭 app,否则 state 里面的数据都不会被还原!
注意:在主入口创建的 XxxBloc,在主入口处创建了一次,在其它页面均不需要再次创建,在任何页面只需要使用 BlocBuilder,便可以定点刷新及其获取全局 XxxBloc 的 state 数据
使用场景
全局的主题色,字体样式和大小等等全局配置更改;这种情况,在需要全局属性的地方,使用 BlocBuilder 对应的全局 XxxBloc 泛型去刷新数据就行了
跨页面去调用事件,既然是全局的 XxxBloc,这就说明,我们可以在任何页面,使用 BlocProvider.of<XxxBloc>(context)
调用全局 XxxBloc 中事件,这就起到了一种跨页面调用事件的效果
使用全局 Bloc 做跨页面事件时,应该明白,当你关闭 Bloc 对应的页面,对应全局 Bloc 中的并不会被回收,下次进入页面,页面的数据还是上次退出页面修改的数据,这里应该使用 StatefulWidget,在 initState 生命周期处,初始化数据;或者在 dispose 生命周期处,还原数据源
思考下:全局 Bloc 对象存在周期是在整个 App 存活周期,必然不能创建过多的全局 Bloc,跨页面传递事件使用全局 Bloc 应当只能做折中方案
效果图
使用
来看下怎么创建和使用全局 Bloc 吧!
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: MainPage(),
builder: (BuildContext context, Widget child) {
return MultiBlocProvider(
providers: [
///此处通过BlocProvider创建的Bloc或者Cubit是全局的
BlocProvider<SpanOneCubit>(
create: (BuildContext context) => SpanOneCubit(),
),
],
child: child,
);
},
);
}
}
复制代码
需要用俩个 Bloc 模块来演示,这里分别用SpanOneCubit
和SpanTwoCubit
来演示,其中SpanOneCubit
是全局的
SpanOneCubit
class SpanOneState {
int count;
///初始化方法
SpanOneState init() {
return SpanOneState()..count = 0;
}
///克隆方法,针对于刷新界面数据
SpanOneState clone() {
return SpanOneState()..count = count;
}
}
复制代码
view
这个页面仅仅是展示计数变量的变化,因为在主入口使用了BlocProvider
创建了SpanOneCubit
,所以在这个页面不需要再次创建,直接使用BlocBuilder
便可以获取其 state
可以发现,这个页面使用了 StatefulWidget,在 initState 周期中,初始化了数据源;这样,每次进入页面,数据源就不会保存为上一次改动的来,都会被初始化为我们想要的值;这个页面能接受到任何页面调用其事件,这样就实现类似于广播的一种效果(伪
)
class CubitSpanOnePage extends StatefulWidget {
@override
_SpanOnePageState createState() => _SpanOnePageState();
}
class _SpanOnePageState extends State<CubitSpanOnePage> {
@override
void initState() {
BlocProvider.of<BlocSpanOneCubit>(context).init();
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Text('跨页面-One')),
floatingActionButton: FloatingActionButton(
onPressed: () => BlocProvider.of<BlocSpanOneCubit>(context).toSpanTwo(),
child: const Icon(Icons.arrow_forward_outlined),
),
body: Center(
child: BlocBuilder<BlocSpanOneCubit, BlocSpanOneState>(
builder: (context, state) {
return Text(
'SpanTwoPage点击了 ${state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
);
}
}
复制代码
class SpanOneCubit extends Cubit<SpanOneState> {
SpanOneCubit() : super(SpanOneState().init());
void init() {
emit(state.init());
}
///跳转到跨页面
void toSpanTwo(BuildContext context) {
Navigator.push(context, MaterialPageRoute(builder: (context) => SpanTwoPage()));
}
///自增
void increase() {
state..count = ++state.count;
emit(state.clone());
}
}
复制代码
SpanTwoCubit
state
使用 count,记录下我们点击自增的次数
class SpanTwoState {
int count;
///初始化方法
SpanTwoState init() {
return SpanTwoState()..count = 0;
}
///克隆方法,针对于刷新界面数据
SpanTwoState clone() {
return SpanTwoState()..count = count;
}
}
复制代码
class CubitSpanTwoPage extends StatelessWidget {
final bloc = BlocSpanTwoCubit();
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (context) => bloc..init(context),
child: Scaffold(
backgroundColor: Colors.white,
appBar: AppBar(title: Text('跨页面-Two')),
floatingActionButton: FloatingActionButton(
onPressed: () {
//改变SpanOneCubit模块数据
BlocProvider.of<BlocSpanOneCubit>(context).increase();
//改变当前页面数据
bloc.increase();
},
child: const Icon(Icons.add),
),
body: Center(
child: BlocBuilder<BlocSpanTwoCubit, BlocSpanTwoState>(
builder: (context, state) {
return Text(
'当前点击了 ${state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
),
);
}
}
复制代码
class SpanTwoCubit extends Cubit<SpanTwoState> {
SpanTwoCubit() : super(SpanTwoState().init());
void init(BuildContext context){
emit(state.init());
}
///自增
void increase() => emit(state.clone()..count = ++state.count);
}
复制代码
总结
OK,这样便用全局 Bloc 实现了类似广播的一种效果
Bloc API 说明
BlocBuilder
BlocBuilder 是 Flutter 窗口小部件,需要Bloc
和builder
函数。BlocBuilder
处理构建小部件以响应新状态。BlocBuilder
与非常相似,StreamBuilder
但具有更简单的 API,可以减少所需的样板代码量。该builder
函数可能会被多次调用,并且应该是一个纯函数,它会根据状态返回小部件。
看看BlocListener
是否要响应状态更改“执行”任何操作,例如导航,显示对话框等。
如果省略 cubit 参数,BlocBuilder
将使用BlocProvider
和当前函数自动执行查找BuildContext
。
BlocBuilder<BlocA, BlocAState>(
builder: (context, state) {
// return widget here based on BlocA's state
}
)
复制代码
仅当您希望提供一个范围仅限于单个窗口小部件且无法通过父级BlocProvider
和当前类访问的 bloc 时,才指定该 bloc BuildContext
。
BlocBuilder<BlocA, BlocAState>(
cubit: blocA, // provide the local cubit instance
builder: (context, state) {
// return widget here based on BlocA's state
}
)
复制代码
为了对何时builder
调用该函数进行细粒度的控制,buildWhen
可以提供一个可选的选项。buildWhen
获取先前的块状态和当前的块状态并返回一个布尔值。如果buildWhen
返回 true,builder
将使用进行调用,state
并且小部件将重新生成。如果buildWhen
返回 false,builder
则不会调用state
且不会进行重建。
BlocBuilder<BlocA, BlocAState>(
buildWhen: (previousState, state) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
复制代码
BlocProvider
BlocProvider 是 Flutter 小部件,可通过为其子元素提供块BlocProvider.of<T>(context)
。它用作依赖项注入(DI)小部件,以便可以将一个块的单个实例提供给子树中的多个小部件。
在大多数情况下,BlocProvider
应使用它来创建新的 bloc,这些 bloc 将可用于其余子树。在这种情况下,由于BlocProvider
负责创建块,它将自动处理关闭 bloc。
BlocProvider(
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
复制代码
默认情况下,BlocProvider
将懒惰地创建 bloc,这意味着create
当通过查找块时将执行该 bloc BlocProvider.of<BlocA>(context)
。
要覆盖此行为并强制create
立即运行,lazy
可以将其设置为false
。
BlocProvider(
lazy: false,
create: (BuildContext context) => BlocA(),
child: ChildA(),
);
复制代码
在某些情况下,BlocProvider
可用于向小部件树的新部分提供现有的 bloc。当需要将现有 bloc 用于新路线时,这将是最常用的。在这种情况下,BlocProvider
由于不会创建 bloc,因此不会自动关闭该 bloc。
BlocProvider.value(
value: BlocProvider.of<BlocA>(context),
child: ScreenA(),
);
复制代码
然后从ChildA
或ScreenA
中检索BlocA
:
// with extensions
context.read<BlocA>();
// without extensions
BlocProvider.of<BlocA>(context)复制到剪贴板错误复制的
复制代码
MultiBlocProvider
MultiBlocProvider 是 Flutter 小部件,可将多个BlocProvider
小部件合并为一个。 MultiBlocProvider
提高了可读性,消除了嵌套多个元素的需求BlocProviders
。通过使用,MultiBlocProvider
我们可以从:
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
child: BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
child: BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
child: ChildA(),
)
)
)
复制代码
至:
MultiBlocProvider(
providers: [
BlocProvider<BlocA>(
create: (BuildContext context) => BlocA(),
),
BlocProvider<BlocB>(
create: (BuildContext context) => BlocB(),
),
BlocProvider<BlocC>(
create: (BuildContext context) => BlocC(),
),
],
child: ChildA(),
)
复制代码
BlocListener
BlocListener 是 Flutter 小部件,它带有BlocWidgetListener
和一个可选Bloc
,listener
以响应 bloc 中的状态变化。它应用于需要在每次状态更改时发生一次的功能,例如导航,显示 a SnackBar
,显示 aDialog
等。
listener`与in和函数不同,每次状态更改(**不**包括初始状态)仅被调用一次。`builder``BlocBuilder``void
复制代码
如果省略 cubit 参数,BlocListener
将使用BlocProvider
和当前函数自动执行查找BuildContext
。
BlocListener<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
复制代码
仅当您希望提供无法通过BlocProvider
和当前访问的 bloc 时,才指定该 bloc BuildContext
。
BlocListener<BlocA, BlocAState>(
cubit: blocA,
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container()
)
复制代码
为了对何时listener
调用该函数进行细粒度的控制,listenWhen
可以提供一个可选的选项。listenWhen
获取先前的 bloc 状态和当前的 bloc 状态并返回一个布尔值。如果listenWhen
返回 true,listener
将使用调用state
。如果listenWhen
返回 false,listener
则不会调用state
。
BlocListener<BlocA, BlocAState>(
listenWhen: (previousState, state) {
// return true/false to determine whether or not
// to call listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
child: Container(),
)
复制代码
MultiBlocListener
MultiBlocListener 是 Flutter 小部件,可将多个BlocListener
小部件合并为一个。 MultiBlocListener
提高了可读性,消除了嵌套多个元素的需求BlocListeners
。通过使用,MultiBlocListener
我们可以从:
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
child: BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
child: BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
child: ChildA(),
),
),
)
复制代码
至:
MultiBlocListener(
listeners: [
BlocListener<BlocA, BlocAState>(
listener: (context, state) {},
),
BlocListener<BlocB, BlocBState>(
listener: (context, state) {},
),
BlocListener<BlocC, BlocCState>(
listener: (context, state) {},
),
],
child: ChildA(),
)
复制代码
BlocConsumer
BlocConsumer 公开builder
和listener
以便对新状态做出反应。BlocConsumer
与嵌套类似BlocListener
,BlocBuilder
但减少了所需的样板数量。BlocConsumer
仅应在需要重建 UI 和执行其他对状态更改进行响应的情况下使用cubit
。BlocConsumer
取需要BlocWidgetBuilder
和BlocWidgetListener
和任选的cubit
,BlocBuilderCondition
和BlocListenerCondition
。
如果cubit
省略该参数,BlocConsumer
将使用BlocProvider
和当前函数自动执行查找 BuildContext
。
BlocConsumer<BlocA, BlocAState>(
listener: (context, state) {
// do stuff here based on BlocA's state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
复制代码
可选的listenWhen
,buildWhen
可以实现,以更精细地控制何时listener
和builder
被调用。在listenWhen
和buildWhen
将在每个被调用cubit
state
的变化。它们各自采用先前的state
和当前的,state
并且必须返回 a bool
,以确定是否将调用builder
and / orlistener
函数。以前state
会被初始化为state
的cubit
的时候BlocConsumer
被初始化。listenWhen
并且buildWhen
是可选的,如果未实现,则默认为true
。
BlocConsumer<BlocA, BlocAState>(
listenWhen: (previous, current) {
// return true/false to determine whether or not
// to invoke listener with state
},
listener: (context, state) {
// do stuff here based on BlocA's state
},
buildWhen: (previous, current) {
// return true/false to determine whether or not
// to rebuild the widget with state
},
builder: (context, state) {
// return widget here based on BlocA's state
}
)
复制代码
RepositoryProvider
RepositoryProvider 是 Flutter 小部件,它通过为其子节点提供存储库RepositoryProvider.of<T>(context)
。它用作依赖项注入(DI)小部件,以便可以将存储库的单个实例提供给子树中的多个小部件。BlocProvider
应该用于提供块,而RepositoryProvider
只能用于存储库。
RepositoryProvider(
create: (context) => RepositoryA(),
child: ChildA(),
);
复制代码
然后ChildA
我们可以通过以下方式检索Repository
实例:
// with extensions
context.read<RepositoryA>();
// without extensions
RepositoryProvider.of<RepositoryA>(context)
复制代码
MultiRepositoryProvider
MultiRepositoryProvider 是 Flutter 小部件,将多个RepositoryProvider
小部件合并为一个。 MultiRepositoryProvider
提高了可读性,消除了嵌套多个元素的需求RepositoryProvider
。通过使用,MultiRepositoryProvider
我们可以从:
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
child: RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
child: RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
child: ChildA(),
)
)
)
复制代码
至:
MultiRepositoryProvider(
providers: [
RepositoryProvider<RepositoryA>(
create: (context) => RepositoryA(),
),
RepositoryProvider<RepositoryB>(
create: (context) => RepositoryB(),
),
RepositoryProvider<RepositoryC>(
create: (context) => RepositoryC(),
),
],
child: ChildA(),
)
复制代码
最后
相关地址
系列文章
评论