如何理解这张图
在代码中体现 ui 1、监听状态,更新widget 2、功能交互,向其它widget 发送事件
BlocBuilder<LoginPageCubit, LoginPageState>( builder: (context, state) { return Visibility( visible: state.phoneNum.isNotEmpty, child: Container( margin: EdgeInsets.only(right: 16. rdp), alignment: Alignment.centerRight, child: GestureDetector( child: SvgPicture.asset(LoginImg.LOGIN_IC_CLOSE), onTap: () { _phoneController.clear(); context.read<LoginPageCubit>().setPhoneNum("" ); }, )), ); },
data 维护了整个widget所需要的状态,data中数据的更新会体现到ui上,同时,ui上更新了数据也需要同步到状态机中。
1、业务利用bloc的机制实现状态机
class LoginPageCubit extends Cubit <LoginPageState > { LoginPageCubit() : super (LoginPageState()); void setPhoneNum(String phoneNum) { ... emit(state.copyWith(phoneNum: phoneNum)); } }
2、业务注册状态机
return MultiBlocProvider( providers: [ BlocProvider(create: (context) => LoginPageCubit()), ], child: Scaffold(resizeToAvoidBottomInset: false , body: LoginView()));
bloc bloc提供的各种api:BlocProvider
、BlocBuilder
、以及emit()
,起到桥的作用,完成状态机和ui桥接工作。
进阶使用 在Flutter_Bloc中常用的api有这些
BlocListener 基础监听器,其它的衍生Listener都是基于这个api实现,同时,它也可以单独使用。 有的时候,我们只需要监听状态更新,不需要更新UI,例如:监听到手机号为空时,弹出一个toast。这种情况,就可以使用BlocListener
实现需求
BlocListener<LoginPageCubit, LoginPageState>( listener: (context, state){ print ("接收到状态更新,弹出toast" ); }, child: Container( margin: EdgeInsets.only(top: 2.5 .rdp), child: CustomCheckbox( ... ), ) ),
BlocSelector 在data
中我们创建了一个状态机,进行更新状态,状态机中的状态有很多个:手机号、验证码、隐私协议同意等等。正常情况下,只要更新了状态,所有的BlocBuilder
都会更新。
是否有一种更加颗粒化的方式,更新手机号的状态,就只有手机号的widget会刷新,其它widget不需要刷新。
针对这种情况,bloc也提供了对应的api来满足更加颗粒化的刷新。BlocSelector<B extends StateStreamable<S>, S, T>
,T 为传递给build
的状态
BlocSelector<LoginPageCubit, LoginPageState, bool >( selector: (state){ return state.isAgree; }, builder: (context, state) { return Container( margin: EdgeInsets.only(top: 2.5 .rdp), child: CustomCheckbox( value: state, onChanged: (newValue) { context.read<LoginPageCubit>().setProtocolState(newValue); }, size: 14. rdp, customIcon: SvgPicture.asset(state ? CommonImg.COMMON_IC_CB_CHECKED : CommonImg.COMMON_IC_CB_UNCHECKED), ), ); }, ),
BlocBuilder
效果,我在下面的隐私协议中监听了状态机的状态,上面的手机号输入会更新状态机的状态,因此隐私协议的widget也出现了更新。
Blocselector
效果,使用了Blocselector
后,隐私协议的widget只会关心isAgree
这个状态,手机号的输入并不会导致隐私协议的widget出现更新。
BlocConsumer 有的时候,我们需要在状态更新的时候做两件事: 1、弹出一个toast、打开一个dialog 2、并同时需要更新widget
如果使用的是BlocBuilder
,看起来可以在builder
中直接弹toast,eg:
builder: (context, state) { SmartDialog.showToast("xxxx" ); return Container( margin: EdgeInsets.only(top: 2.5 .rdp), ... ) }
看起来是没问题,但是在实际运行中会出现下面错误
[VERBOSE-2:dart_vm_initializer.cc(41)] Unhandled Exception: 'package:flutter/src/widgets/overlay.dart': Failed assertion: line 457 pos 12: '!_entries.contains(entry)': The specified entry is already present in the Overlay.
具体的原因?
这种情况下就可以使用BlocConsumer
来实现需求
BlocConsumer<LoginPageCubit, LoginPageState>( listener: (context, state) { SmartDialog.showToast("xxxx" ); }, builder: (context, state) { return Container( margin: EdgeInsets.only(top: 2.5 .rdp), .... ); }, ),
MultiBlocListener 有时候,也许一个page特别复杂,需要多个状态机协作,就可以用到MultiBlocListener
源码 bloc的架构是典型的发布-订阅模式,通过Stream
来实现发布-订阅功能,使用Provider
实现了widget中cubit
的全局共享。
bloc发布订阅 Cubit 在[[#如何理解这张图]]中,首先看看Cubit
是怎么创建的
class LoginPageCubit extends Cubit <LoginPageState > { LoginPageCubit() : super (LoginPageState()); .... }
bloc_base.dart
abstract class BlocBase <State > implements StateStreamableSource <State >, Emittable <State >, ErrorSink { BlocBase(this ._state) { _blocObserver.onCreate(this ); } late final _stateController = StreamController<State>.broadcast(); }
在Cubit
中,创建了一个StreamController
,在这里发布者和订阅是一对多的关系,因此它使用了broadcast
模式。
Listener 以BlocBuilder
,我们是如何订阅事件的
Padding( child: BlocBuilder<AreaCodeCubit, AreaCodeEvent>( builder: (context, state) { return Text( ..... ); }, ), ),
我们在BlocBuilder
的builder
构建widget,BlocBuilder
使用BlocListener
更新widget
@override Widget build(BuildContext context) { if (widget.bloc == null ) { context.select<B, bool >((bloc) => identical(_bloc, bloc)); } return BlocListener<B, S>( bloc: _bloc, listenWhen: widget.buildWhen, listener: (context, state) => setState(() => _state = state), child: widget.build(context, _state), ); }
在BlocListener中,从Context取出Cubit
,进行事件订阅bloc_listener.dart
void _subscribe() { _subscription = _bloc.stream.listen((state) { if (widget.listenWhen?.call(_previousState, state) ?? true ) { widget.listener(context, state); } _previousState = state; }); }
单监听到新状态是,BlocListener的listener中使用setState
更新状态,触发builder: (context, state)
的重建,实现wiget的刷新。
由于builder中的widget属于BlocBuilder
,因此setState只刷新BlocBuilder中的widget,这样达到了局部刷新的效果。
Porvider FlutterBloc还讲到了Cubit的共享,先看看Cubit
是怎么注册的。
BlocProvider(create: (context) => LoginPageCubit())
在flutter_bloc
中,还用到了一个状态管理的库Provider
bloc_provider.dart
Widget buildWithChild(BuildContext context, Widget? child) { assert ( child != null , '$runtimeType used outside of MultiBlocProvider must specify a child' , ); final value = _value; return value != null ? InheritedProvider<T>.value( value: value, startListening: _startListening, lazy: lazy, child: child, ) : InheritedProvider<T>( create: _create, dispose: (_, bloc) => bloc.close(), startListening: _startListening, child: child, lazy: lazy, ); }
在inherited_provider.dart
class _CreateInheritedProviderState <T > extends _DelegateState <T , _CreateInheritedProvider <T >> { VoidCallback? _removeListener; bool _didInitValue = false ; T? _value; _CreateInheritedProvider<T>? _previousWidget; FlutterErrorDetails? _initError; @override T get value { .... _value = delegate.create!(element!); .... } }
Cubit
已经注册到了Context,那么如何取出Cubit
呢,先看看如何取出
context.read<LoginPageCubit>().setPhoneNum("" );
provider.dart
T read<T>() { return Provider.of<T>(this , listen: false ); }
provider.dart
static T of<T>(BuildContext context, {bool listen = true }) { .... final inheritedElement = _inheritedElementOf<T>(context); if (listen) { } final value = inheritedElement?.value; if (_isSoundMode) { if (value is ! T) { throw ProviderNullException(T, context.widget.runtimeType); } return value; } return value as T; }
Bloc的状态管理 状态机 在flutter-bloc
中,有状态机的概念,ui元素和状态机一一对应,状态中值的修改可以自动映射到ui上,同样的,ui上对应元素的修改也要反馈到状态机。
bloc是一个发布-订阅的消息框架,flutter-bloc
在针对flutter做了进一步的封装,更加符合数据驱动的方式。
使用BlocListener监听event,然后使用setState
更新widget.build(context, _state)
,从而实现widget的更新。
状态管理 Flutter 常见的状态管理方式有下面几种:
Widget 管理自己的状态
Widget 管理子 Widget 状态
混合管理(父 Widget 和子 Widget 都管理状态)
Bloc是一个状态管理库,可以实现上面的几种管理方式,那么Bloc是如何实现状态管理的呢? 回顾下Bloc的模型:
1、状态机注册的时候,BlocProvider
和BuildContext
绑定在了一起。
2、Widget需要发送事件时需要从context读取provider,从而取到状态机。
context.read<LoginPageCubit>().setPhoneNum("" );
3、如果需要监听事件,需要使用BlocListener
监听并构建widget
children: [ BlocSelector<LoginPageCubit, LoginPageState, bool >( selector: (state) { return state.isAgree; }, builder: (context, state) { return Container( ... ); }, ), Expanded(child: ProtocolText()) ],
4、BlocListener收到事件时,通过setState
更新Widget
单元测试 bloc/packages/bloc/test at master · felangel/bloc (github.com)
group('constructor' , () { late BlocObserver observer; setUp(() { observer = MockBlocObserver(); Bloc.observer = observer; }); test('triggers onCreate on observer' , () { final cubit = CounterCubit(); verify(() => observer.onCreate(cubit)).called(1 ); }); }); group('emit' , (){ test('throws StateError if cubit is closed' , () { var didThrow = false ; runZonedGuarded(() { final cubit = CounterCubit(); expectLater( cubit.stream, emitsInOrder(<Matcher>[equals(1 ), emitsDone]), ); cubit ..increment() ..close() ..increment(); }, (error, _) { didThrow = true ; expect( error, isA<StateError>().having( (e) => e.message, 'message' , 'Cannot emit new states after calling close' , ), ); }); expect(didThrow, isTrue); }); test('emits states in the correct order' , () async { final states = <int >[]; final cubit = CounterCubit(); final subscription = cubit.stream.listen(states.add); cubit.increment(); await cubit.close(); await subscription.cancel(); expect(states, [1 ]); }); }
bloc/packages/flutter_bloc/test/bloc_consumer_test.dart at master · felangel/bloc (github.com)
group('BlocConsumer' , (){ testWidgets( 'accesses the bloc directly and passes initial state to builder and ' 'nothing to listener' , (tester) async { final counterCubit = CounterCubit(); final listenerStates = <int >[]; await tester.pumpWidget( MaterialApp( home: Scaffold( body: BlocConsumer<CounterCubit, int >( bloc: counterCubit, builder: (context, state) { return Text('State: $state ' ); }, listener: (_, state) { listenerStates.add(state); }, ), ), ), ); expect(find.text('State: 0' ), findsOneWidget); expect(listenerStates, isEmpty); }); testWidgets( 'accesses the bloc directly ' 'and passes multiple states to builder and listener' , ( tester) async { final counterCubit = CounterCubit(); final listenerStates = <int >[]; await tester.pumpWidget( MaterialApp( home: Scaffold( body: BlocConsumer<CounterCubit, int >( bloc: counterCubit, builder: (context, state) { return Text('State: $state ' ); }, listener: (_, state) { listenerStates.add(state); }, ), ), ), ); expect(find.text('State: 0' ), findsOneWidget); expect(listenerStates, isEmpty); counterCubit.increment(); await tester.pump(); expect(find.text('State: 1' ), findsOneWidget); expect(listenerStates, [1 ]); }); }
Vector Landscape Vectors by Vecteezy