foreword
About some of the content of this article, I wanted to write it a long time ago, but there is no source of motivation, and I have been pigeon
This time I was urged several times by the Jett boss, and finally I finished writing this article. The article contains some of my thoughts and opinions on state management, hoping to resonate with the vast crowd. . .
Awareness of state management
change
Decoupling is the cornerstone of many ideas or frameworks
Take the most classic MVC as an example, the modules are divided into three layers uniformly
- Model layer: data management
- Controller layer: logic processing
- View layer: view construction
This classic hierarchical division can handle many scenarios
- MVP, MVVM is also a variant of MVC, which is essentially for more reasonable decoupling in suitable scenarios
- In fact, these modes are very suitable for the mobile terminal. The old way of writing XML on the mobile terminal is to get its View node and then operate on it.
- In the era of JSP, JQuery is very popular, manipulating DOM nodes and refreshing data; it is exactly the same.
The era of is always advancing in development, and technology is constantly changing; just like Prometheus stealing fire, bringing many changes to the world
For the idea of View node operation, the fixed application is inaccurate in today's front-end
Nowadays, the front end is displayed by many "states" to control the interface, and it needs to be explained in a more refined language.
Inclusive
The focus of state management is on the surface: state and management
- In just four words, it sums up the mind and its soul
The state is the soul of the page and the anchor of business logic and general logic. As long as the state is separated and managed, the page can be decoupled
In general, from the concept of state management, multiple levels can be decoupled
minimalist mode 😃
This is a very concise hierarchical division, and many popular Flutter state management frameworks are also divided in this way, such as: provider, getx
- view: interface layer
- Logic: logic layer + state layer
standard mode 🙂
This is already a hierarchical division similar to MVC, which is also very common, such as: cubit (provider and getx can also easily divide this structure)
- view: interface
- Logic: Logic layer
- State: state layer
strict mode 😐
For the standard vertebra mode, it has been divided in place, but there is still a certain type of level that has not been divided: User and program interaction behavior
Explain: If you want to divide this level, the cost must be very large, which will further increase the complexity of the use of the framework
- Later, we will analyze why dividing this level will lead to a lot of cost.
Common state management frameworks: Bloc, Redux, fish_redux
- view: interface layer
- Logic: Logic layer
- State: state layer
- Action: Behavior layer
OCD mode 😑
Common state management frameworks: Redux, fish_redux
From the diagram, this structure is already a bit complicated, and a huge cost is paid for decoupling the data refresh level.
- view: interface layer
- Logic: Logic layer
- State: state layer
- Action: Behavior layer
- Reducer: This layer is dedicated to processing data changes
think
Should we fear and resist changing things and thoughts?
I often think: thought witnesses changes, it does not decay in time, but becomes more and more
Example: Design Mode
cost of decoupling
Separate logic + state layer
A mature state management framework must divide the logic from the interface layer. This is the most simple original intention of a state management framework.
some
In fact, the cost paid at this time is for the framework developers, and the developers need to choose a suitable technical solution for reasonable decoupling.
To implement a state management framework, at this point, I might be able to say:
- It's not that hard
- A few files to implement a reasonable and powerful state management framework
At this point, you may be thinking in front of the screen: just you? I almost laughed out loud
Regarding the above, I am not bragging. After reading several source code of state management, I found that the idea of state management is actually very simple. Of course, the code of open source framework is not that simple. Basically, a lot of abstraction has been done to facilitate functions Expansion, this will basically cause great trouble to the reader, especially the provider, the scalp is numb,,,
After I extracted several typical state management ideas, I reproduced their operation mechanism with minimalist code, and found that all the ideas used were observation mode. After understanding, I don’t think the state management framework is so mysterious.
I have no contempt for any thought: They are all great pioneers in that wild age!
How does the logic + state layer from the interface?
I have summarized several classic state management implementation mechanisms, because each implementation source code is a bit long, so I put it in the second half of the article, and you can take a look if you are interested; the code for each implementation method is complete , which can run independently
Decouple the logic layer interface
- The cost is on the framework side, requiring a more complex implementation
- Generally speaking, only two layers are decoupled, which is generally simpler to use.
Decoupled State Layer
- If the logic layer is separated and the state layer is decoupled, generally speaking, it is not difficult; it can be simply divided manually. Several idea plugins I wrote generate template code, and all of them have divided this layer.
- It is also possible to force conventions directly inside the framework, Bloc mode and Cubit mode in Bloc, redux series. . .
- The cost of division is not high, the cost of use is not high, and the impact of decoupling at this layer is far-reaching
Action layer cost
What is the Action layer? Just like its name, the behavior layer, the interaction events on the user and the interface can be divided into this layer
- For example: button click events, input events, pull-up and drop-down events, etc.
- The user generates these events on the interface, and we also need to do the corresponding logic to respond
Why divide the Action layer?
- If you have a great time writing flutter nesting doll code, you may find that many interactive entrances for click events are in the widget mountain.
- Interactive events are scattered in a large number of interface codes. If you need to adjust the parameters of the jump event, it will be a headache to find.
- There is another very important aspect: In fact, the entrance of interactive events is the business entrance. When the demand is adjusted, it is also very troublesome to find the corresponding business code!
Based on the consideration that the business will gradually become ghosts and animals, some frameworks divide the Action layer to manage all interaction events in a unified manner.
cost
frame side cost
If you want to manage all interaction events in a unified way, it is not very difficult to implement
- In general, we can directly call the methods of the logic layer directly in the view layer to execute related business logic.
- Now it is necessary to uniformly manage the behavior of calling logic layer methods
- Therefore, it is necessary to add an intermediate layer in the middle of the call to transfer all events
- This transit layer is the action layer, which can manage all interaction events
Let's take a look at the realization idea
The implementation cost on the framework side is not high, mainly the acceptance and distribution of events
In fact, we generally don't care about the cost of the framework side. It doesn't matter how complicated the internal implementation of the framework is. The usage should be concise and clear
If the internal design is very delicate, but it is obscure and cumbersome to use, it will undoubtedly increase the mental burden on the user.
use side cost
Dividing the Action layer will add a certain cost to the user, which is unavoidable
- Event definition cost: Because the event layer is divided, each interaction must be defined in the Action layer
- Cost of sending events: In the view layer, the defined events need to be sent with different APIs. This is not much different from the previous calls, and the cost is very low.
- Logic layer processing cost: The logic layer must have one more module or method, and the distribution method is accepted for classification processing. It will be a bit cumbersome here.
The module in the red box in the figure is the additional cost of use
external performance
Bloc does not use Action
- View layer, code shorthand, just look at its external performance
class BlBlocCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => BlBlocCounterBloc()..init(),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
return Scaffold(
...
floatingActionButton: FloatingActionButton(
//调用业务方法
onPressed: () => bloc.increment(),
child: Icon(Icons.add),
),
);
}
}
- Bloc layer
class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
BlBlocCounterBloc() : super(BlBlocCounterState().init());
void init() async {
///处理逻辑,调用emit方法刷新
emit(state.clone());
}
...
}
- State layer: In this demo, this layer is not important and will not be written
Bloc use Action
- View layer, code shorthand, just look at its external performance
class BlBlocCounterPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (BuildContext context) => BlBlocCounterBloc()..add(InitEvent()),
child: Builder(builder: (context) => _buildPage(context)),
);
}
Widget _buildPage(BuildContext context) {
final bloc = BlocProvider.of<BlBlocCounterBloc>(context);
return Scaffold(
...
floatingActionButton: FloatingActionButton(
onPressed: () => bloc.add(AEvent()),
child: Icon(Icons.add),
),
);
}
}
- Bloc layer: The display code is written before bloc8.0, and the effect is more intuitive; for 8.0 writing, please refer to: Flutter_bloc uses analysis
class BlBlocCounterBloc extends Bloc<BlBlocCounterEvent, BlBlocCounterState> {
BlBlocCounterBloc() : super(BlBlocCounterState().init());
@override
Stream<BlBlocCounterState> mapEventToState(BlBlocCounterEvent event) async* {
if (event is InitEvent) {
yield await init();
} else if (event is AEvent) {
yield a();
} else if (event is BEvent) {
yield b();
} else if (event is CEvent) {
yield c();
} else if (event is DEvent) {
yield d();
} else if (event is EEvent) {
yield e();
} else if (event is FEvent) {
yield f();
} else if (event is GEvent) {
yield g();
} else if (event is HEvent) {
yield h();
} else if (event is IEvent) {
yield i();
} else if (event is JEvent) {
yield j();
} else if (event is KEvent) {
yield k();
}
}
///对应业务方法
...
}
- Event layer (also called Action layer): If you need to pass parameters, you need to define relevant variables in the event class, implement its constructor, and transfer the view layer data to the bloc layer
abstract class BlBlocCounterEvent {}
class InitEvent extends BlBlocCounterEvent {}
class AEvent extends BlBlocCounterEvent {}
class BEvent extends BlBlocCounterEvent {}
class CEvent extends BlBlocCounterEvent {}
.......
class KEvent extends BlBlocCounterEvent {}
- State layer: In this demo, this layer is not important and will not be written
fish_redux also has a division of the Action layer, which is not shown here. Interested friends can learn about it by themselves: fish_redux use detailed explanation
Summary
The above uses the Action layer and does not use the Action layer form comparison, you can find that the difference is quite big
The Action layer is added, which makes the cost of use inevitably soar
Many people may complain at this time in their hearts: so troublesome,,,
Thinking and Evolution of Action Layer
Through the analysis of the design essence of the separated Action layer, we will find an unavoidable reality!
- Increase the Action layer, the cost of the user side is unavoidable
- Because the increased cost on the user side is the core of the design on the frame side
When the business becomes increasingly complex, the division of the Action layer is imperative, and we must summarize the event entry; when the business is frequently adjusted, it is necessary to quickly locate the corresponding business!
Is there a way to simplify ?
The division of the Action layer will increase the burden on the user to a certain extent. Is there any way to simplify it? At the same time, it can achieve the effect of managing event entry?
I have done a lot of thinking about the Widget of the crazy nesting doll in the View layer, and made some attempts to split the form.
After splitting, the View layer and Action are well combined. The specific operations are as follows: Flutter to improve the problem of nesting doll hell (example of imitating the Himalayan PC page)
Look at the code effect after splitting
- Because the View sub-modules are clearly divided, the external exposure method is the business event, which can be easily located to the corresponding business
- After this division, the corresponding page structure becomes very clear, and it is very easy to modify the module corresponding to the page.
After the related transformation of the View layer
- It is very convenient to locate business and interface modules
- At the same time, it also avoids a series of slightly cumbersome operations in the Action layer.
Summary
Framework conventions can standardize many developers with different behaviors
The splitting of the View layer that I propose later can only rely on the awareness of the developers themselves
Here, I give a different way, the choice of which can only be decided by yourself.
I have been using the splitting of the View layer all the time, and I feel very friendly to the maintenance of complex modules in the later period~~
Tucao of the Reducer layer
Maybe it's because I'm too naive to feel the beauty of this layer of differentiation
I also wrote a lot of pages with fish_redux (for a year), and I also passed the relevant data to the Reducer through the Action layer before, and then refreshed accordingly, which caused a problem!
- Every time I refresh the data of different behaviors, I need to create an Action
- Then parse the passed data in the Reducer layer, and then assign values to the clone object. When I want to modify the data, I must first go to the Effect layer to see the logic, and then go to the Reducer to modify the assignment.
- Jump back and forth, trouble to burst!
After being detoured many times and irritated many times, I directly wrote the Reducer layer as a refresh method!
Reducer<WebViewState> buildReducer() {
return asReducer(
<Object, Reducer<WebViewState>>{
WebViewAction.onRefresh: _onRefresh,
},
);
}
WebViewState _onRefresh(WebViewState state, Action action) {
return state.clone();
}
Even in complex modules, I didn't feel the benefits he brought to me, so I could only weaken him infinitely into a refresh method.
Several implementations of state management
This is the source code of some state management I looked at
- Summarized several refresh mechanisms for state management
- Choose one, you can create your own state management framework
Several previous source code analysis articles have been written, sorted out, and summarized.
Realization of Rotten Street
is the least difficult to implement ⭐
This is a very common implementation
- This is a simple, easy to use, powerful implementation
- At the same time, because the difficulty is not high, it is also a kind of bad street realization.
accomplish
needs to implement a middleware that manages an instance of the logic layer: implementation of dependency injection
You can also use InheritedWidget to save and transfer logic layer instances (this is what Bloc does); but managing it yourself can greatly broaden the usage scenarios. Here, we implement a middleware that manages instances by ourselves.
- Only three basic APIs are implemented here
///依赖注入,外部可将实例,注入该类中,由该类管理
class Easy {
///注入实例
static T put<T>(T dependency, {String? tag}) =>
_EasyInstance().put(dependency, tag: tag);
///获取注入的实例
static T find<T>({String? tag, String? key}) =>
_EasyInstance().find<T>(tag: tag, key: key);
///删除实例
static bool delete<T>({String? tag, String? key}) =>
_EasyInstance().delete<T>(tag: tag, key: key);
}
///具体逻辑
class _EasyInstance {
factory _EasyInstance() => _instance ??= _EasyInstance._();
static _EasyInstance? _instance;
_EasyInstance._();
static final Map<String, _InstanceInfo> _single = {};
///注入实例
T put<T>(T dependency, {String? tag}) {
final key = _getKey(T, tag);
//只保存第一次注入:针对自动刷新机制优化,每次热重载的时候,数据不会重置
_single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
return find<T>(tag: tag);
}
///获取注入的实例
T find<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
var info = _single[newKey];
if (info?.value != null) {
return info!.value;
} else {
throw '"$T" not found. You need to call "Easy.put($T())""';
}
}
///删除实例
bool delete<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
if (!_single.containsKey(newKey)) {
print('Instance "$newKey" already removed.');
return false;
}
_single.remove(newKey);
print('Instance "$newKey" deleted.');
return true;
}
String _getKey(Type type, String? name) {
return name == null ? type.toString() : type.toString() + name;
}
}
class _InstanceInfo<T> {
_InstanceInfo(this.value);
T value;
}
Define a listener and base class
- ChangeNotifier can also be used; here we simply define a
class EasyXNotifier {
List<VoidCallback> _listeners = [];
void addListener(VoidCallback listener) => _listeners.add(listener);
void removeListener(VoidCallback listener) {
for (final entry in _listeners) {
if (entry == listener) {
_listeners.remove(entry);
return;
}
}
}
void dispose() => _listeners.clear();
void notify() {
if (_listeners.isEmpty) return;
for (final entry in _listeners) {
entry.call();
}
}
}
- I wrote a minimalist place here, and the relevant life cycle has not been added. For the sake of code simplicity, this is not for the time being.
class EasyXController {
EasyXNotifier xNotifier = EasyXNotifier();
///刷新控件
void update() => xNotifier.notify();
}
Let's take a look at the core EasyBuilder control: that's it!
- The realization of the code is extremely simple, I hope everyone can have a clear idea
///刷新控件,自带回收机制
class EasyBuilder<T extends EasyXController> extends StatefulWidget {
final Widget Function(T logic) builder;
final String? tag;
final bool autoRemove;
const EasyBuilder({
Key? key,
required this.builder,
this.autoRemove = true,
this.tag,
}) : super(key: key);
@override
_EasyBuilderState<T> createState() => _EasyBuilderState<T>();
}
class _EasyBuilderState<T extends EasyXController> extends State<EasyBuilder<T>> {
late T controller;
@override
void initState() {
super.initState();
///此处是整个类的灵魂代码
controller = Easy.find<T>(tag: widget.tag);
controller.xNotifier.addListener(() {
if (mounted) setState(() {});
});
}
@override
void dispose() {
if (widget.autoRemove) {
Easy.delete<T>(tag: widget.tag);
}
controller.xNotifier.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) => widget.builder(controller);
}
use
- It is very simple to use, first look at the logic layer
class EasyXCounterLogic extends EasyXController {
var count = 0;
void increase() {
++count;
update();
}
}
- interface layer
class EasyXCounterPage extends StatelessWidget {
final logic = Easy.put(EasyXCounterLogic());
@override
Widget build(BuildContext context) {
return BaseScaffold(
appBar: AppBar(title: const Text('EasyX-自定义EasyBuilder刷新机制')),
body: Center(
child: EasyBuilder<EasyXCounterLogic>(builder: (logic) {
return Text(
'点击了 ${logic.count} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => logic.increase(),
child: Icon(Icons.add),
),
);
}
}
- renderings
Implementation of InheritedWidget
implementation with some difficulty ⭐⭐
More detailed analysis can be viewed: The other side of Flutter Provider
Let's take a look at InheritedWidget, it comes with some functions
- Store data, and data can be passed with parent and child nodes
- Built-in local refresh mechanism
data transfer
partial refresh
InheritedWidget has a powerful operation function for the Element of the child node
- You can store the element instance of the child widget in the _dependents variable in its own InheritedElement
- Calling its notifyClients method will traverse the child Elements in _dependents, and then call the markNeedsBuild method of the child Element to complete the fixed-point refresh of the child nodes.
With the above two key knowledge, you can easily implement a powerful state management framework. Let's see how to implement
accomplish
- ChangeNotifierEasyP: Analogy Provider's ChangeNotifierProvider
class ChangeNotifierEasyP<T extends ChangeNotifier> extends StatelessWidget {
ChangeNotifierEasyP({
Key? key,
required this.create,
this.builder,
this.child,
}) : super(key: key);
final T Function(BuildContext context) create;
final Widget Function(BuildContext context)? builder;
final Widget? child;
@override
Widget build(BuildContext context) {
assert(
builder != null || child != null,
'$runtimeType must specify a child',
);
return EasyPInherited(
create: create,
child: builder != null
? Builder(builder: (context) => builder!(context))
: child!,
);
}
}
class EasyPInherited<T extends ChangeNotifier> extends InheritedWidget {
EasyPInherited({
Key? key,
required Widget child,
required this.create,
}) : super(key: key, child: child);
final T Function(BuildContext context) create;
@override
bool updateShouldNotify(InheritedWidget oldWidget) => false;
@override
InheritedElement createElement() => EasyPInheritedElement(this);
}
class EasyPInheritedElement<T extends ChangeNotifier> extends InheritedElement {
EasyPInheritedElement(EasyPInherited<T> widget) : super(widget);
bool _firstBuild = true;
bool _shouldNotify = false;
late T _value;
late void Function() _callBack;
T get value => _value;
@override
void performRebuild() {
if (_firstBuild) {
_firstBuild = false;
_value = (widget as EasyPInherited<T>).create(this);
_value.addListener(_callBack = () {
// 处理刷新逻辑,此处无法直接调用notifyClients
// 会导致owner!._debugCurrentBuildTarget为null,触发断言条件,无法向后执行
_shouldNotify = true;
markNeedsBuild();
});
}
super.performRebuild();
}
@override
Widget build() {
if (_shouldNotify) {
_shouldNotify = false;
notifyClients(widget);
}
return super.build();
}
@override
void notifyDependent(covariant InheritedWidget oldWidget, Element dependent) {
//此处就直接刷新添加的监听子Element了,不各种super了
dependent.markNeedsBuild();
// super.notifyDependent(oldWidget, dependent);
}
@override
void unmount() {
_value.removeListener(_callBack);
_value.dispose();
super.unmount();
}
}
- EasyP: Provider class analogous to Provider
class EasyP {
/// 获取EasyP实例
/// 获取实例的时候,listener参数老是写错,这边直接用俩个方法区分了
static T of<T extends ChangeNotifier>(BuildContext context) {
return _getInheritedElement<T>(context).value;
}
/// 注册监听控件
static T register<T extends ChangeNotifier>(BuildContext context) {
var element = _getInheritedElement<T>(context);
context.dependOnInheritedElement(element);
return element.value;
}
/// 获取距离当前Element最近继承InheritedElement<T>的组件
static EasyPInheritedElement<T>
_getInheritedElement<T extends ChangeNotifier>(BuildContext context) {
var inheritedElement = context
.getElementForInheritedWidgetOfExactType<EasyPInherited<T>>()
as EasyPInheritedElement<T>?;
if (inheritedElement == null) {
throw EasyPNotFoundException(T);
}
return inheritedElement;
}
}
class EasyPNotFoundException implements Exception {
EasyPNotFoundException(this.valueType);
final Type valueType;
@override
String toString() => 'Error: Could not find the EasyP<$valueType>';
}
- build: The last whole Build class will do
class EasyPBuilder<T extends ChangeNotifier> extends StatelessWidget {
const EasyPBuilder(
this.builder, {
Key? key,
}) : super(key: key);
final Widget Function() builder;
@override
Widget build(BuildContext context) {
EasyP.register<T>(context);
return builder();
}
}
done. The above three classes implement a set of state management framework based on the functions of InheritedWidget
- Implemented partial refresh function
- Implemented an instance of the logic layer, which can pass functions with the Widget parent and child nodes
use
The usage is basically the same as Provider...
- view
class CounterEasyPPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return ChangeNotifierEasyP(
create: (BuildContext context) => CounterEasyP(),
builder: (context) => _buildPage(context),
);
}
Widget _buildPage(BuildContext context) {
final easyP = EasyP.of<CounterEasyP>(context);
return Scaffold(
appBar: AppBar(title: Text('自定义状态管理框架-EasyP范例')),
body: Center(
child: EasyPBuilder<CounterEasyP>(() {
return Text(
'点击了 ${easyP.count} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => easyP.increment(),
child: Icon(Icons.add),
),
);
}
}
- easyP
class CounterEasyP extends ChangeNotifier {
int count = 0;
void increment() {
count++;
notifyListeners();
}
}
- Effect picture:
Implementation of automatic refresh
implementation needs some inspiration ⭐⭐⭐
Implementation of automatic refresh
- A connection is established between a single state variable and a refresh component
- Once the variable value changes, the refresh component automatically refreshes
- When a state changes, only its refresh component will be automatically triggered, and other refresh components will not be triggered.
accomplish
Similarly, the middleware that manages its logic class needs to be managed; in order to complete the example, write down this dependency management class
///依赖注入,外部可将实例,注入该类中,由该类管理
class Easy {
///注入实例
static T put<T>(T dependency, {String? tag}) =>
_EasyInstance().put(dependency, tag: tag);
///获取注入的实例
static T find<T>({String? tag, String? key}) =>
_EasyInstance().find<T>(tag: tag, key: key);
///删除实例
static bool delete<T>({String? tag, String? key}) =>
_EasyInstance().delete<T>(tag: tag, key: key);
}
///具体逻辑
class _EasyInstance {
factory _EasyInstance() => _instance ??= _EasyInstance._();
static _EasyInstance? _instance;
_EasyInstance._();
static final Map<String, _InstanceInfo> _single = {};
///注入实例
T put<T>(T dependency, {String? tag}) {
final key = _getKey(T, tag);
//只保存第一次注入:针对自动刷新机制优化,每次热重载的时候,数据不会重置
_single.putIfAbsent(key, () => _InstanceInfo<T>(dependency));
return find<T>(tag: tag);
}
///获取注入的实例
T find<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
var info = _single[newKey];
if (info?.value != null) {
return info!.value;
} else {
throw '"$T" not found. You need to call "Easy.put($T())""';
}
}
///删除实例
bool delete<T>({String? tag, String? key}) {
final newKey = key ?? _getKey(T, tag);
if (!_single.containsKey(newKey)) {
print('Instance "$newKey" already removed.');
return false;
}
_single.remove(newKey);
print('Instance "$newKey" deleted.');
return true;
}
String _getKey(Type type, String? name) {
return name == null ? type.toString() : type.toString() + name;
}
}
class _InstanceInfo<T> {
_InstanceInfo(this.value);
T value;
}
- Customize a listener class
class EasyXNotifier {
List<VoidCallback> _listeners = [];
void addListener(VoidCallback listener) => _listeners.add(listener);
void removeListener(VoidCallback listener) {
for (final entry in _listeners) {
if (entry == listener) {
_listeners.remove(entry);
return;
}
}
}
void dispose() => _listeners.clear();
void notify() {
if (_listeners.isEmpty) return;
for (final entry in _listeners) {
entry.call();
}
}
}
the automatic refresh mechanism, the basic type needs to be encapsulated
- The main logic is in Rx<T>
- set value and get value are key
///拓展函数
extension IntExtension on int {
RxInt get ebs => RxInt(this);
}
extension StringExtension on String {
RxString get ebs => RxString(this);
}
extension DoubleExtension on double {
RxDouble get ebs => RxDouble(this);
}
extension BoolExtension on bool {
RxBool get ebs => RxBool(this);
}
///封装各类型
class RxInt extends Rx<int> {
RxInt(int initial) : super(initial);
RxInt operator +(int other) {
value = value + other;
return this;
}
RxInt operator -(int other) {
value = value - other;
return this;
}
}
class RxDouble extends Rx<double> {
RxDouble(double initial) : super(initial);
RxDouble operator +(double other) {
value = value + other;
return this;
}
RxDouble operator -(double other) {
value = value - other;
return this;
}
}
class RxString extends Rx<String> {
RxString(String initial) : super(initial);
}
class RxBool extends Rx<bool> {
RxBool(bool initial) : super(initial);
}
///主体逻辑
class Rx<T> {
EasyXNotifier subject = EasyXNotifier();
Rx(T initial) {
_value = initial;
}
late T _value;
bool firstRebuild = true;
String get string => value.toString();
@override
String toString() => value.toString();
set value(T val) {
if (_value == val && !firstRebuild) return;
firstRebuild = false;
_value = val;
subject.notify();
}
T get value {
if (RxEasy.proxy != null) {
RxEasy.proxy!.addListener(subject);
}
return _value;
}
}
needs to write a very important transit class, which also stores the listener object of the responsive variable
- This class has very core logic: it associates reactive variables with refresh controls!
class RxEasy {
EasyXNotifier easyXNotifier = EasyXNotifier();
Map<EasyXNotifier, String> _listenerMap = {};
bool get canUpdate => _listenerMap.isNotEmpty;
static RxEasy? proxy;
void addListener(EasyXNotifier notifier) {
if (!_listenerMap.containsKey(notifier)) {
//变量监听中刷新
notifier.addListener(() {
//刷新ebx中添加的监听
easyXNotifier.notify();
});
//添加进入map中
_listenerMap[notifier] = '';
}
}
}
Refresh Control Ebx
typedef WidgetCallback = Widget Function();
class Ebx extends StatefulWidget {
const Ebx(this.builder, {Key? key}) : super(key: key);
final WidgetCallback builder;
@override
_EbxState createState() => _EbxState();
}
class _EbxState extends State<Ebx> {
RxEasy _rxEasy = RxEasy();
@override
void initState() {
super.initState();
_rxEasy.easyXNotifier.addListener(() {
if (mounted) setState(() {});
});
}
Widget get notifyChild {
final observer = RxEasy.proxy;
RxEasy.proxy = _rxEasy;
final result = widget.builder();
if (!_rxEasy.canUpdate) {
throw 'Widget lacks Rx type variables';
}
RxEasy.proxy = observer;
return result;
}
@override
Widget build(BuildContext context) {
return notifyChild;
}
@override
void dispose() {
_rxEasy.easyXNotifier.dispose();
super.dispose();
}
}
In the automatic refresh mechanism, reclaiming dependent instances requires processing
Here I wrote a recycling control that can complete the automatic recycling of instances
- The meaning of the name, bind the instance to the control, when the control is recycled, the logic layer instance will also be automatically recycled
class EasyBindWidget extends StatefulWidget {
const EasyBindWidget({
Key? key,
this.bind,
this.tag,
this.binds,
this.tags,
required this.child,
}) : assert(
binds == null || tags == null || binds.length == tags.length,
'The binds and tags arrays length should be equal\n'
'and the elements in the two arrays correspond one-to-one',
),
super(key: key);
final Object? bind;
final String? tag;
final List<Object>? binds;
final List<String>? tags;
final Widget child;
@override
_EasyBindWidgetState createState() => _EasyBindWidgetState();
}
class _EasyBindWidgetState extends State<EasyBindWidget> {
@override
Widget build(BuildContext context) {
return widget.child;
}
@override
void dispose() {
_closeController();
_closeControllers();
super.dispose();
}
void _closeController() {
if (widget.bind == null) {
return;
}
var key = widget.bind.runtimeType.toString() + (widget.tag ?? '');
Easy.delete(key: key);
}
void _closeControllers() {
if (widget.binds == null) {
return;
}
for (var i = 0; i < widget.binds!.length; i++) {
var type = widget.binds![i].runtimeType.toString();
if (widget.tags == null) {
Easy.delete(key: type);
} else {
var key = type + (widget.tags?[i] ?? '');
Easy.delete(key: key);
}
}
}
}
use
- logic layer
class EasyXEbxCounterLogic {
RxInt count = 0.ebs;
///自增
void increase() => ++count;
}
- Interface layer: An EasyBindWidget is set at the top node of the page to ensure that the dependency injection instance can be automatically recycled
class EasyXEbxCounterPage extends StatelessWidget {
final logic = Easy.put(EasyXEbxCounterLogic());
@override
Widget build(BuildContext context) {
return EasyBindWidget(
bind: logic,
child: BaseScaffold(
appBar: AppBar(title: const Text('EasyX-自定义Ebx刷新机制')),
body: Center(
child: Ebx(() {
return Text(
'点击了 ${logic.count.value} 次',
style: TextStyle(fontSize: 30.0),
);
}),
),
floatingActionButton: FloatingActionButton(
onPressed: () => logic.increase(),
child: Icon(Icons.add),
),
),
);
}
}
- renderings
finally
In general, this article has done some thinking and a little personal opinion on the various levels of state management. The second half of the article also gives some implementation plans for state management.
The content in the article should be helpful to those who want to design state management; if you have different opinions, please discuss in the comment area
- Article demo address: flutter_use
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。