Preface
After reading the Bloc source code, I feel a bit complicated. . .
say something positive...
Those who have used Bloc can definitely feel that the Bloc framework has made a clear division of the development page, and the framework has forced two development modes.
Bloc mode: This mode is divided into four layers of structure
- bloc: logical layer
- state: data layer
- event: all interaction events
- view: page
Cubit mode: This mode divides a three-layer structure
- cubit: logical layer
- state: data layer
- view: page
The author is still very old in the division of levels. The state layer is written directly inside the framework. This layer must be separated separately; I feel that if it had not been pitted by the Cthulhu code mountain of large projects, it would not have been so. Deep obsession
This state layer is added, I think it is quite necessary, because once a page maintains a lot of state, mixing state variables and logic methods together, the later maintenance will be very headache.
say something critical...
You may be in the group and often see some older brothers saying: Bloc encapsulates the Provider.
- Here I confirm: This is true, Bloc does seal the Provider
- However, only the child node in the Provider is used to query the data of the nearest parent node's InheritedElement and the top-level Widget parallel layout function. The most classic refresh mechanism of the Provider is completely useless!
I think the author of Bloc may be a little confused about the refresh mechanism of Provider.
- Even if the bloc framework uses a line in the build widget: Provider.of<T>(context, listen: true) or remove e.markNeedsNotifyDependents() , I won’t say that. . .
- The Bloc framework did some operations that made me very puzzled. The callback in the _startListening method called e.markNeedsNotifyDependents() , completely useless ! Provider.of<T>(context, listen: true) not used to add a child Element to InheritedElement, it is a refreshing loneliness! In order to verify my idea, I notifyClients method of the framework layer. When the emit or yield refresh is called, the map of _dependents is always empty, hey. . .
I have complained a lot, it’s not that I have any opinion on bloc
- I also used Bloc for a long time, in-depth use of the process, some optimizations to its usage, and also wrote a code generation plug-in for it, I also paid some time and energy for it.
- But: code will not lie, all the good or bad are in it, and you can feel it with your heart.
Why do you feel complicated?
When I was looking at the Provider source code before, I had a headache. The internal logic was indeed a bit complicated, but the overall process was clear and the logic was refreshed. It was a hearty feeling! After the pain, there is a huge sense of satisfaction, and I am amazed by the Provider's proficient use of various APIs in the Framework layer, and then the wonderful refresh mechanism!
Then, as mentioned above, I did spend some energy on Bloc to optimize its use, then read his source code, and then think about the Provider source code I saw before, and suddenly I felt a huge gap.
In my opinion, such a famous open source library, the above lumps can be completely avoided; perhaps it is this inexplicable high expectation that gave me such a gap. . .
the way, maybe the author of Bloc, deliberately left a Provider refresh mechanism in Bloc, using this as an easter egg!
suddenly felt that the pimple was gone!
use
Here is the introduction and some adjustments have been made to the official usage
For the process of adjusting your mind, please refer to: flutter_bloc usage analysis---Sao Nian, are you still using bloc!
The following will directly write the post-adjustment writing
Plug-in
Because the wording generated by the official plug-in is a bit different from the adjusted wording, and the official plug-in does not support the generation of the view layer and related settings, here I will use a plug-in to improve the related functions
Please note that the Wrap code and prompt code snippets are based on the official plug-in rules
Wrap Widget rules come: intellij_generator_plugin
The shortcut code generation rule comes: intellij_generator_plugin
- flutter bloc in Android Studio
- Generate template code
- Support to modify the suffix
- Wrap Widget (alt + enter):RepositoryProvider,BlocConsumer,BlocBuilder,BlocProvider,BlocListener
- Enter bloc to generate shortcut code snippets
usage
The plug-in can generate two mode codes: Bloc and Cubit; take a look
Cubit mode
- view
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => CounterCubit(),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final cubit = BlocProvider.of<CounterCubit>(context);
return Container();
}
}
- cubit
class CounterCubit extends Cubit<CounterState> {
CounterCubit() : super(CounterState().init());
}
- state
class CounterState {
CounterState init() {
return CounterState();
}
CounterState clone() {
return CounterState();
}
}
Bloc mode
- view: an initialization event is added by default
class CounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => CounterBloc()..add(InitEvent()),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<CounterBloc>(context);
return Container();
}
}
- bloc
class CounterBloc extends Bloc<CounterEvent, CounterState> {
CounterBloc() : super(CounterState().init());
@override
Stream<CounterState> mapEventToState(CounterEvent event) async* {
if (event is InitEvent) {
yield await init();
}
}
Future<CounterState> init() async {
return state.clone();
}
}
- event
abstract class CounterEvent {}
class InitEvent extends CounterEvent {}
- state
class CounterState {
CounterState init() {
return CounterState();
}
CounterState clone() {
return CounterState();
}
}
to sum up
The Bloc and Cubit modes are very clear about the structure. Because there are multiple layers of structure, there must be corresponding template codes and files. Without the help of plug-ins, it will be very uncomfortable to write these template codes every time; here for everyone I wrote this plug-in, if there is any bug, please give us feedback in time. . .
How to use it will not be repeated here, please refer to the usage details: flutter_bloc usage analysis---Sao Nian, are you still using bloc!
Pre-knowledge
To understand the principle of Bloc, you need to understand the relevant knowledge of Stream
StreamController, StreamBuilder: The combination of these two can also easily refresh the partial Widget, let’s see how to use it
- view: The Stream must be closed, and StatefulWidget needs to be used here, and its dispose callback is needed
class StreamPage extends StatefulWidget {
const StreamPage({Key? key}) : super(key: key);
@override
_StreamPageState createState() => _StreamPageState();
}
class _StreamPageState extends State<StreamPage> {
final logic = StreamLogic();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Bloc-Bloc范例')),
body: Center(
child: StreamBuilder<StreamState>(
initialData: logic.state,
stream: logic.stream,
builder: (context, snapshot) {
return Text(
'点击了 ${snapshot.data!.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => logic.increment(),
child: Icon(Icons.add),
),
);
}
@override
void dispose() {
logic.dispose();
super.dispose();
}
}
- logic: The Stream data source is generic, you can directly use the basic types. The entity is used here to expand more data later
class StreamLogic {
final state = StreamState();
// 实例化流控制器
final _controller = StreamController<StreamState>.broadcast();
Stream<StreamState> get stream => _controller.stream;
void increment() {
_controller.add(state..count = ++state.count);
}
void dispose() {
// 关闭流控制器,释放资源
_controller.close();
}
}
- state
class StreamState {
int count = 0;
}
- Effect picture
Actually, after reading the above usage, you will find that there are several troublesome places
- A series of objects that need to create a Stream
- Stream must be closed, so use StatefulWidget
- StreamBuilder needs to write three parameters, which is very troublesome
The Bloc author borrowed the InheritedProvider control of Provider to solve the above pain points.
Refresh mechanism
The refresh mechanism of Bloc is very simple. The Stream operation above basically clarifies its core refresh mechanism, but the author of Bloc has done some encapsulation, let’s take a look
The charm of BlocProvider
BlocProvider is a very important control. The simplification of refresh parameters and the closure of Stream are related to it, because it should encapsulate the InheritedProvider in a Provider; however, in my opinion, it is still a very attractive control.
- BlocProvider: The source code of BlocProvider is very simple, here is the source code of this class
class BlocProvider<T extends BlocBase<Object?>>
extends SingleChildStatelessWidget with BlocProviderSingleChildWidget {
/// {@macro bloc_provider}
BlocProvider({
Key? key,
required Create<T> create,
this.child,
this.lazy,
}) : _create = create,
_value = null,
super(key: key, child: child);
BlocProvider.value({
Key? key,
required T value,
this.child,
}) : _value = value,
_create = null,
lazy = null,
super(key: key, child: child);
/// Widget which will have access to the [Bloc] or [Cubit].
final Widget? child;
final bool? lazy;
final Create<T>? _create;
final T? _value;
static T of<T extends BlocBase<Object?>>(
BuildContext context, {
bool listen = false,
}) {
try {
return Provider.of<T>(context, listen: listen);
} on ProviderNotFoundException catch (e) {
if (e.valueType != T) rethrow;
throw FlutterError(
'''
BlocProvider.of() called with a context that does not contain a $T.
No ancestor could be found starting from the context that was passed to BlocProvider.of<$T>().
This can happen if the context you used comes from a widget above the BlocProvider.
The context used was: $context
''',
);
}
}
@override
Widget buildWithChild(BuildContext context, Widget? 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,
);
}
static VoidCallback _startListening(
InheritedContext<BlocBase> e,
BlocBase value,
) {
final subscription = value.stream.listen(
(dynamic _) => e.markNeedsNotifyDependents(),
);
return subscription.cancel;
}
}
The difference between
Looking at the source code above, we can see: BlocProvider.value does not automatically close the Stream.
- So BlocProvider.value should not be used in a normal single page, it can be used in a global Bloc instance
- For single page Bloc, please use BlocProvider to create Bloc or Cubit
create is an externally instantiated XxxBloc, which is finally passed into the InheritedProvider
- create is the XxxBloc instance passed in from outside
The instance is directly passed into the InheritedProvider, which is related to the Provider, and is finally stored in _InheritedProviderScopeElement, and _startListening is also the content of the Provider
- This internal principle is more complicated and very important. If you are interested, please see: Source Code: The other side of Flutter Provider (4D graphics + plug-in)
Seriously, the logic in _startListening is useless
- The markNeedsNotifyDependents api is specially made by the Provider author for refreshing the Provider child element. It must be matched with Provider.of<T>(context, listen: true) to register the Widget control.
- There is too much logic involved, all in the above Provider source code analysis article, you can check it out if you are interested
BlocProvider.of<T>
- Function: can obtain the XxxBloc 160f6d9b00ebe1 passed in by BlocProvider Create in the sub-control of the BlocProvider
- Please note: If you use the BlocProvider parent layout context, XxxBloc cannot be obtained, it must be the child layout of BlocProvider
Principle: source article: the other side of Flutter Provider (4D graphics + plugin) , still in this article
- I'm really not promoting this article, BlocProvider part, Bloc uses too many Provider features
- Provider article, I spent a lot of effort to analyze the principle, here, there is no need to do a repeater
summary: to summarize the role of the
- BlocProvider may store the XxxBloc instance passed in from the outside, and the XxxBloc class must inherit BlocBase
- The XxxBloc instance stored by BlocProvider can be obtained through BlocProvider.of<T> (must be in BlocProvider or its child Widget)
- The instance XxxBloc obtained by BlocProvider can be automatically released; the XxxBloc instance of the BlocProvider.value named constructor will not be released automatically
BlocProvider implements the functions of the above three bunkers, and basically can completely streamline the use of Stream.
- Icon
Cornerstone BlocBase
Needless to say, BlocBase is an important abstract class
- BlocBase
abstract class BlocBase<State> {
BlocBase(this._state) {
Bloc.observer.onCreate(this);
}
StreamController<State>? __stateController;
StreamController<State> get _stateController {
return __stateController ??= StreamController<State>.broadcast();
}
State _state;
bool _emitted = false;
State get state => _state;
Stream<State> get stream => _stateController.stream;
@Deprecated(
'Use stream.listen instead. Will be removed in v8.0.0',
)
StreamSubscription<State> listen(
void Function(State)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) {
return stream.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);
}
void emit(State state) {
if (_stateController.isClosed) return;
if (state == _state && _emitted) return;
onChange(Change<State>(currentState: this.state, nextState: state));
_state = state;
_stateController.add(_state);
_emitted = true;
}
@mustCallSuper
void onChange(Change<State> change) {
Bloc.observer.onChange(this, change);
}
@mustCallSuper
void addError(Object error, [StackTrace? stackTrace]) {
onError(error, stackTrace ?? StackTrace.current);
}
@protected
@mustCallSuper
void onError(Object error, StackTrace stackTrace) {
Bloc.observer.onError(this, error, stackTrace);
assert(() {
throw BlocUnhandledErrorException(this, error, stackTrace);
}());
}
@mustCallSuper
Future<void> close() async {
Bloc.observer.onClose(this);
await _stateController.close();
}
}
on 160f6d9b00eebc has done a few more important things to sort out
Bloc.observer is not important, it is a class defined in the framework, which can be ignored here, it is not important
Stored the passed state object
- Each time you use emit to refresh, the incoming state will be replaced with the state object stored before
- Emit made a judgment. If the incoming state and the stored state object are the same, the refresh operation will not be performed (this is the reason why I added the clone method in the State class)
- Initialized a series of objects
- Encapsulates the operation of closing the Stream stream
- Simplify the above code
abstract class BlocBase<T> {
BlocBase(this.state) : _stateController = StreamController<T>.broadcast();
final StreamController<T> _stateController;
T state;
bool _emitted = false;
Stream<T> get stream => _stateController.stream;
void emit(T newState) {
if (_stateController.isClosed) return;
if (state == newState && _emitted) return;
state = newState;
_stateController.add(state);
_emitted = true;
}
@mustCallSuper
Future<void> close() async {
await _stateController.close();
}
}
BlocBuilder
BlocBuilder has done a lot of streamlining the usage of StreamBuilder, let’s take a look at the internal implementation
BlocBuilder
- Here you need to pay attention to the builder parameter; buildWhen is a parameter to judge whether it needs to be updated
- build method calls builder , you need to look at the parent class BlocBuilderBase
typedef BlocWidgetBuilder<S> = Widget Function(BuildContext context, S state);
class BlocBuilder<B extends BlocBase<S>, S> extends BlocBuilderBase<B, S> {
const BlocBuilder({
Key? key,
required this.builder,
B? bloc,
BlocBuilderCondition<S>? buildWhen,
}) : super(key: key, bloc: bloc, buildWhen: buildWhen);
final BlocWidgetBuilder<S> builder;
@override
Widget build(BuildContext context, S state) => builder(context, state);
}
BlocBuilderBase
- context.read<B>() and Provider.of<T>(this, listen: false) have the same effect, which is an encapsulation of the latter
- Here we get the XxxBloc object passed in in BlocProvider through context.read<B>(), and assign it to the _bloc variable in _BlocBuilderBaseState
- BlocBuilderBase abstracts a build method, which is assigned to BlocListener in _BlocBuilderBaseState
- BlocBuilderBase has not been able to see the refresh logic. Several important parameters: _bloc, listener, widget.build are all passed to BlocListener; you need to look at the implementation of BlocListener
abstract class BlocBuilderBase<B extends BlocBase<S>, S>
extends StatefulWidget {
const BlocBuilderBase({Key? key, this.bloc, this.buildWhen})
: super(key: key);
final B? bloc;
final BlocBuilderCondition<S>? buildWhen;
Widget build(BuildContext context, S state);
@override
State<BlocBuilderBase<B, S>> createState() => _BlocBuilderBaseState<B, S>();
}
class _BlocBuilderBaseState<B extends BlocBase<S>, S>
extends State<BlocBuilderBase<B, S>> {
late B _bloc;
late S _state;
@override
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_state = _bloc.state;
}
...
@override
Widget build(BuildContext context) {
...
return BlocListener<B, S>(
bloc: _bloc,
listenWhen: widget.buildWhen,
listener: (context, state) => setState(() => _state = state),
child: widget.build(context, _state),
);
}
}
- BlocListener: The parameters are passed to the constructor of the parent class, you need to look at the implementation of the parent class BlocListenerBase
class BlocListener<B extends BlocBase<S>, S> extends BlocListenerBase<B, S>
const BlocListener({
Key? key,
required BlocWidgetListener<S> listener,
B? bloc,
BlocListenerCondition<S>? listenWhen,
Widget? child,
}) : super(
key: key,
child: child,
listener: listener,
bloc: bloc,
listenWhen: listenWhen,
);
}
- BlocListenerBase: Some logic codes have been streamlined
abstract class BlocListenerBase<B extends BlocBase<S>, S>
extends SingleChildStatefulWidget {
const BlocListenerBase({
Key? key,
required this.listener,
this.bloc,
this.child,
this.listenWhen,
}) : super(key: key, child: child);
final Widget? child;
final B? bloc;
final BlocWidgetListener<S> listener;
final BlocListenerCondition<S>? listenWhen;
@override
SingleChildState<BlocListenerBase<B, S>> createState() =>
_BlocListenerBaseState<B, S>();
}
class _BlocListenerBaseState<B extends BlocBase<S>, S>
extends SingleChildState<BlocListenerBase<B, S>> {
StreamSubscription<S>? _subscription;
late B _bloc;
late S _previousState;
@override
void initState() {
super.initState();
_bloc = widget.bloc ?? context.read<B>();
_previousState = _bloc.state;
_subscribe();
}
...
@override
Widget buildWithChild(BuildContext context, Widget? child) {
return child!;
}
@override
void dispose() {
_unsubscribe();
super.dispose();
}
void _subscribe() {
_subscription = _bloc.stream.listen((state) {
if (widget.listenWhen?.call(_previousState, state) ?? true) {
widget.listener(context, state);
}
_previousState = state;
});
}
void _unsubscribe() {
_subscription?.cancel();
_subscription = null;
}
}
finally found the key code!
It can be found that Bloc is refreshed through the cooperation of StreamController and listen.
Called widget.listener(context, state), the method of this implementation is setState, you can take a look at the class _BlocBuilderBaseState
_bloc.stream.listen(
(state) {
if (widget.listenWhen?.call(_previousState, state) ?? true) {
widget.listener(context, state);
}
_previousState = state;
},
);
Streamline BlocBuild
The implementation logic of BlocBuild above is still too convoluted, and there are too many encapsulation levels. Write a simplified version of BlocBuild below.
Of course, it will definitely retain the core logic of BlocBuild refresh
class BlocEasyBuilder<T extends BlocBase<V>, V> extends StatefulWidget {
const BlocEasyBuilder({
Key? key,
required this.builder,
}) : super(key: key);
final Function(BuildContext context, V state) builder;
@override
_BlocEasyBuilderState createState() => _BlocEasyBuilderState<T, V>();
}
class _BlocEasyBuilderState<T extends BlocBase<V>, V>
extends State<BlocEasyBuilder<T, V>> {
late T _bloc;
late V _state;
StreamSubscription<V>? _listen;
@override
void initState() {
_bloc = BlocProvider.of<T>(context);
_state = _bloc.state;
//数据改变刷新Widget
_listen = _bloc.stream.listen((event) {
setState(() {});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return widget.builder(context, _state);
}
@override
void dispose() {
_listen?.cancel();
super.dispose();
}
}
- Take a look at the renderings: For detailed usage code, please check: flutter_use
Event mechanism
If you use the Bloc model to develop, there will be an additional Event layer, which defines all event interactions
Mention here
- Bloc: some code omitted
abstract class Bloc<Event, State> extends BlocBase<State> {
/// {@macro bloc}
Bloc(State initialState) : super(initialState) {
_bindEventsToStates();
}
StreamSubscription<Transition<Event, State>>? _transitionSubscription;
StreamController<Event>? __eventController;
StreamController<Event> get _eventController {
return __eventController ??= StreamController<Event>.broadcast();
}
void add(Event event) {
if (_eventController.isClosed) return;
try {
onEvent(event);
_eventController.add(event);
} catch (error, stackTrace) {
onError(error, stackTrace);
}
}
Stream<Transition<Event, State>> transformEvents(
Stream<Event> events,
TransitionFunction<Event, State> transitionFn,
) {
return events.asyncExpand(transitionFn);
}
@protected
@visibleForTesting
@override
void emit(State state) => super.emit(state);
Stream<State> mapEventToState(Event event);
Stream<Transition<Event, State>> transformTransitions(
Stream<Transition<Event, State>> transitions,
) {
return transitions;
}
@override
@mustCallSuper
Future<void> close() async {
await _eventController.close();
await _transitionSubscription?.cancel();
return super.close();
}
void _bindEventsToStates() {
_transitionSubscription = transformTransitions(
transformEvents(
_eventController.stream,
(event) => mapEventToState(event).map(
(nextState) => Transition(
currentState: state,
event: event,
nextState: nextState,
),
),
),
).listen(
(transition) {
if (transition.nextState == state && _emitted) return;
try {
emit(transition.nextState);
} catch (error, stackTrace) {
onError(error, stackTrace);
}
},
onError: onError,
);
}
}
The overall logic is clearer, let’s sort it out
Bloc is an abstract class
- Call the _bindEventsToStates() method in the constructor
- Bloc abstracts a mapEventToState (Event event) method, which inherits the Bloc abstract class and must implement this method
In the Bloc class, an instance of the Stream object is used as the event trigger mechanism of Event
- When an Event event is added, the listener callback in the _bindEventsToStates() method will be triggered
Some operations are done in _bindEventsToStates
- Event event added: events.asyncExpand(transitionFn); first pass its own Event parameters into the transitionFn method for execution
- The logic of transitionFn is: Pass the Event parameter into mapEventToState, and then mapEventToState returns the State object
- Then trigger the listen callback, in the listen, pass the state to the emit, and then trigger the refresh control to rebuild
to sum up
After the analysis of the above several key classes, the operating mechanism of the entire Bloc is immediately clear.
BlocProvider
- Responsible for storing incoming XxxBloc for storage
- The provided of method can get the stored XxxBloc at the location of BlocProvider or its child nodes
- Provide callbacks for recycling resources (recycling Stream stream)
BlocBase
- Stored the passed state object
- A series of Stream objects are initialized
- Encapsulates the operation of closing the Stream stream
BlocBuilder
- The essence is StatefulWidget
- Get XxxBloc through BlocProvider, and then monitor data changes through its listener method
After the data is changed, rebuild the StatefulWidget through setState to achieve the effect of partial refresh
# Hand rub a state management framework
The principle of Bloc is much simpler than that of Provider. . .
Imitate Bloc's refresh mechanism and create a state management framework by hand! Use EasyC to name it!
Hand rub
- EasyC: First, you need to write a base class to handle a series of Stream operations
abstract class EasyC<T> {
EasyC(this.state) : _controller = StreamController<T>.broadcast();
final StreamController<T> _controller;
T state;
bool _emitted = false;
Stream<T> get stream => _controller.stream;
void emit(T newState) {
if (_controller.isClosed) return;
if (state == newState && _emitted) return;
state = newState;
_controller.add(state);
_emitted = true;
}
@mustCallSuper
Future<void> close() async {
await _controller.close();
}
}
EasyCProvider
- The InheritedProvider provided by the Provider framework is not used here.
- Here I used InheritedWidget to rub one
- The of method and the closing of the stream are done; there is no need to manually close the stream, and no need to write a StatefulWidget!
class EasyCProvider<T extends EasyC> extends InheritedWidget {
EasyCProvider({
Key? key,
Widget? child,
required this.create,
}) : super(key: key, child: child ?? Container());
final T Function(BuildContext context) create;
@override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
@override
InheritedElement createElement() => EasyCInheritedElement(this);
static T of<T extends EasyC>(BuildContext context) {
var inheritedElement =
context.getElementForInheritedWidgetOfExactType<EasyCProvider<T>>()
as EasyCInheritedElement<T>?;
if (inheritedElement == null) {
throw 'not found';
}
return inheritedElement.value;
}
}
class EasyCInheritedElement<T extends EasyC> extends InheritedElement {
EasyCInheritedElement(EasyCProvider<T> widget) : super(widget);
bool _firstBuild = true;
late T _value;
T get value => _value;
@override
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_value = (widget as EasyCProvider<T>).create(this);
}
super.performRebuild();
}
@override
void unmount() {
_value.close();
super.unmount();
}
}
- EasyCBuilder: Finally, a fixed-point refresh Widget
class EasyCBuilder<T extends EasyC<V>, V> extends StatefulWidget {
const EasyCBuilder({
Key? key,
required this.builder,
}) : super(key: key);
final Function(BuildContext context, V state) builder;
@override
_EasyCBuilderState createState() => _EasyCBuilderState<T, V>();
}
class _EasyCBuilderState<T extends EasyC<V>, V>
extends State<EasyCBuilder<T, V>> {
late T _easyC;
late V _state;
StreamSubscription<V>? _listen;
@override
void initState() {
_easyC = EasyCProvider.of<T>(context);
_state = _easyC.state;
//数据改变刷新Widget
_listen = _easyC.stream.listen((event) {
setState(() {});
});
super.initState();
}
@override
Widget build(BuildContext context) {
return widget.builder(context, _state);
}
@override
void dispose() {
_listen?.cancel();
super.dispose();
}
}
The above three files basically reproduce the refresh mechanism of Bloc
At the same time, I also got rid of a knot in my heart. The Bloc source code used the _startListening method of the Provider inexplicably. . .
use
The use is basically the same as Bloc
I originally wanted to remove the judgment of the comparison between the two new and old state objects of emit, but think about it, the author of Bloc seems to have a deep obsession with this concept, and has dealt with it in many places; therefore, I also keep it here. Can retain the original usage of Bloc
- view
class CounterEasyCPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return EasyCProvider(
create: (BuildContext context) => CounterEasyC(),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final easyC = EasyCProvider.of<CounterEasyC>(context);
return Scaffold(
appBar: AppBar(title: Text('自定义状态管理框架-EasyC范例')),
body: Center(
child: EasyCBuilder<CounterEasyC, CounterEasyCState>(
builder: (context, state) {
return Text(
'点击了 ${easyC.state.count} 次',
style: TextStyle(fontSize: 30.0),
);
},
),
),
floatingActionButton: FloatingActionButton(
onPressed: () => easyC.increment(),
child: Icon(Icons.add),
),
);
}
}
- logic
class CounterEasyC extends EasyC<CounterEasyCState> {
CounterEasyC() : super(CounterEasyCState().init());
///自增
void increment() => emit(state.clone()..count = ++state.count);
}
- state
class CounterEasyCState {
late int count;
CounterEasyCState init() {
return CounterEasyCState()..count = 0;
}
CounterEasyCState clone() {
return CounterEasyCState()..count = count;
}
}
- Effect picture
Global is also possible, no different from Provider, I will not repeat it here
to sum up
This hand-wrapped EasyC framework retains the essence of the Bloc refresh mechanism, and at the same time, it has also done a lot of streamlining
I believe that if you look at it carefully, you will be able to understand it.
The source code of Bloc is not complicated. It is the use of Stream, which has made a big streamlining, and basically uses the pain points, all of which are encapsulated and processed internally.
At last
message board
The source code parsing of Provider and Bloc is finally finished, and there is only one last one, GetX. . .
In order to prove that the analysis source code I wrote is useful and effective, at the end, I hand-made a brand new state management framework
Choosing a state management framework should be a more prudent thing; you can first look at its principles and understand its internal operating mechanism, and then you can choose on-demand, because you understand its internal operating mechanism, even if You can calmly deal with any problems that arise during use; If you are afraid that the author will abandon the pit or are not satisfied with its function, choose the refresh mechanism you want and rub one yourself!
Provider, Bloc, GetX these three frameworks, I have written the corresponding plug-ins, if your choice of state management framework is any of these three, I believe these plug-ins can help you complete some repetitive workload
Related address
- The Github address of the Demo in the article: flutter_use
Web effect: https://cnad666.github.io/flutter_use/web/index.html
- If you don’t see the relevant function button, you may need to clear the browser cache
Windows: Windows platform installation package
- Password: xdd666
series of articles
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。