auto_binding

Creator: coderz1093

Last updated:

0 purchases

auto_binding Image
auto_binding Images

Languages

Categories

Add to Cart

Description:

auto binding

AutoBinding v2 #
en cn
AutoBinding is a lightweight MVVM bidirectional binding state management framework for data sharing and synchronization.
AutoBinding v2 adopts a new responsive programming approach, inspired by Vue and React, The new version of v2 allows for
the use of existing widgets and build() extensions, which are a regular widget and build() that were originally non
bidirectional, without the need to refactor a large number of WidgetTree hierarchical relationships and smoothly
establish binding relationships.
Compared to the older version of v1, data providers no longer need to force the establishment of model classes for
binding, data callers do not need to forcibly inherit specific State and StatelessWidgets, and dynamic binding does not
require manual release;
The overall design principle of the v2 version is to leave the data structure of the data provider to the maximum extent
possible for developers, and to leave the binding method of the data caller to the maximum extent possible for
developers.
Setup #
flutter pub add auto_binding
copied to clipboard
Data provider #
The system provides four
ways: ModelStatefulWidget, ModelStatelessWidget, DataStatefulWidget, DataStatelessWidget
ModelStatefulWidget #
ModelStatefulWidget provides a model parameter and gives it a generic, which can be directly used as a WidgetTree in
build(), making it easier to use existing data types;
/// add WidgetTree
@override
Widget build(BuildContext context) {
return ModelStatefulWidget<LoginForm>(
model: LoginForm("", ""),
child: CallModelState(),
);
}
copied to clipboard

The model data type of the ModelStatefulWidget in the example is LoginForm, provided by LoginForm;
child is a regular widget called;

ModelStatelessWidget #
ModelStatelessWidget, similar to ModelStatefulWidget, also provides a model parameter that can be used directly as
a WidgetTree.
@override
Widget build(BuildContext context) {
return ModelStatelessWidget<LoginForm>(
model: LoginForm("", ""),
child: CallModelStatelessWidget(),
);
}
copied to clipboard

The example is similar to the example of ModelStatefulWidget, but they differ when facing ancestor node refresh
The ModelStatelessWidget is stateless, so the model will be recreated;
The ModelStatefulWidget can still retain data after refreshing;

DataStatefulWidget #
DataStatefulWidget provides an abstract class of StatefulWidget, and developers need to write subclasses to inherit
it Shared data can be freely added as member variables in custom subclasses.
/// ExampleForDataStatefulWidget a subclass of DataStatefulWidget.
class ExampleForDataStatefulWidget extends DataStatefulWidget {
ExampleForDataStatefulWidget({super.key});

@override
ExampleForDataState createState() => ExampleForDataState();
}

/// ExampleForDataState is a subclass of DataState;
class ExampleForDataState extends DataState<ExampleForDataStatefulWidget> {

//// declare sharing data
String username = '';
String password = '';

////


/// CallDataStatefulWidget calling
@override
Widget builder(BuildContext context) => const CallDataStatefulWidget();
}
copied to clipboard

Compared to ModelStatefulWidget and ModelStatelessWidget, DataState provides a code area for freely defining shared data.

DataStatelessWidget #
DataStatelessWidget is a stateless abstract class, and developers need to write its inheritance class to extend shared
data items;
/// Inheritance class of DataStatelessWidget
class ExampleForModelProviderWidget extends DataStatelessWidget {

/// declare sharing data
final loginForm = LoginForm('', '');

///

/// ExampleForDataStatelessWidget calling
ExampleForModelProviderWidget()
: super(child: CallDataStatelessWidget());
}
copied to clipboard

The usage is similar to DataState, and shared data can also be freely defined.

Data calling #
The data call is divided into three steps: creating a constructor, binding references, and binding views;
Create Builder #
Create a Builder through context
@override
Widget build(BuildContext context) {
var node = Binding.mount(context);
}
copied to clipboard

The bound context should adhere to a smaller range as much as possible, as any changes in the context are within the refreshed range.


Binding references #
Two binding methods: direct binding and reference binding
final usernameRef = Ref.fromData(
getter: (ModelStatelessWidget<LoginForm> widget) => widget.model.username,
setter: (ModelStatelessWidget<LoginForm> widget, String username) =>
widget.model.username = username,
);

@override
Widget build(BuildContext context) {
var node = Binding.mount(context);
/// Reference binding: using defined Ref variables
var username = usernameRef(node);

/// Direct binding: provide getter/setter
var password = Ref.fromData(
getter: (ModelStatelessWidget<LoginForm> widget) => widget.model.password,
setter: (ModelStatelessWidget<LoginForm> widget, String password) =>
widget.model.password = password,
).call(node);
...
}
copied to clipboard

When using multiple contexts, the way of reference binding should be considered, so that Ref variables can be reused

Binding views #
Fill a WidgetTree with binding
class ExampleForModelStatelessWidget extends StatelessWidget {

final usernameRef = Ref.fromData(
getter: (ModelStatelessWidget<LoginForm> widget) => widget.model.username,
setter: (ModelStatelessWidget<LoginForm> widget, String username) =>
widget.model.username = username,
);

Widget build(BuildContext context) {
/// connecting context
var node = Binding.mount(context);

var username = usernameRef(node);

/// no bind
username.raw;

var password = Ref.fromData(
getter: (ModelStatelessWidget<LoginForm> widget) => widget.model.password,
setter: (ModelStatelessWidget<LoginForm> widget, String password) =>
widget.model.password = password,
).(node);
return Column(
children: [
const Text('AutoBinding example for ModelStatelessWidget.',
style: TextStyle(
fontSize: 36,
color: Colors.deepOrange,
fontWeight: FontWeight.bold)),
const SizedBox(height: 20),
const Text('轻便的MVVM双向绑定的框架',
style: TextStyle(fontSize: 16, color: Colors.black38)),
const SizedBox(height: 30),

/// binding TextField
BindingTextField(
usernameRef.ref,

/// 传入binding
decoration: const InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
),
style: const TextStyle(
color: Colors.indigo, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),

/// binding TextField
BindingTextField(
Ref.fromData(
getter: (ModelStatelessWidget<LoginForm> widget) => widget.model.password,
setter: (ModelStatelessWidget<LoginForm> widget, String password) =>
widget.model.password = password,
).ref,

/// 传入binding
decoration: const InputDecoration(
labelText: '密码',
hintText: '请输入密码',
),
style: const TextStyle(
color: Colors.indigo, fontWeight: FontWeight.bold),
),
const SizedBox(height: 20),
],
const SizedBox(height: 30),
ElevatedButton(
onPressed: () async {
/// write data, to refresh view
username.notifyChange('来自指定值的修改');
password.notifyChange('来自指定值的修改');
},
style: const ButtonStyle(
backgroundColor:
MaterialStatePropertyAll<Color>(Colors.lightBlue),
foregroundColor:
MaterialStatePropertyAll<Color>(Colors.white),
),
child: const Text('更改当前值'),
),
const SizedBox(height: 30),
Container(
width: double.infinity,
padding: const EdgeInsets.symmetric(
vertical: 4, horizontal: 10),
color: Colors.blueGrey,

/// binding value
child: Text(
'username = ${username.bindChange()}\npassword = ${password.bindChange()}',
)),
);
}
}
copied to clipboard

In the example, bind to the TextField input box through BindingTextField, and bind the value to the Text view through bindChange(). value will not generate binding;
Modify from specified value through username.notifyChange('来自指定值的修改'); Assign values to immediately generate page
refresh;
BindingTextField also provides valueToString and stringToValue for more formatted input and output. For more
bundling methods, please refer to the Bind Control section.

Generate Ref using macros #
Enable macro mode #
see https://dart.dev/language/macros
1. Configure SDK version, 3.5.0-152 or above
# pubspec.yaml
environment:
sdk: '>=3.5.0-152 <4.0.0'
copied to clipboard
2. Startup Parameters -- enable experiment=macros
flutter run --enable-experiment=macros
dart run --enable-experiment=macros
copied to clipboard
3. 编写本地宏
Due to the fact that macros is still in the experimental stage, cross package parsing of Dart Analysis Server syntax is currently not supported.
This will result in newly added libraries, types, properties, and methods of macro not being recognized by static analysis, leading to missing definition errors. But it is correct at runtime.
Therefore, it is necessary to declare the macros of the external package again within this package, as follows:
// lib/macros/auto_binding.dart
import 'package:auto_binding/auto_binding.dart' as auto_binding;

macro class RefCodable extends auto_binding.RefCodable {
const RefCodable();
}

macro class IgnoreRefCodable extends auto_binding.IgnoreRefCodable {
const IgnoreRefCodable();
}
copied to clipboard
The general reason for not being able to cross packages is that Dart Analysis Server static analysis cannot only focus on source code analysis, but also needs to derive information/warning/error based on the analysis-options.yaml rule.
Cross package analysis requires reanalysis of each package,
At present, the Dart Analysis Server is only a single rule, and each package has its own analysis service enabled. The performance loss is too great, and the communication delay will also increase,
So Dart officials still need to redesign in terms of performance and functionality.
This is an official discussion: https://github.com/dart-lang/sdk/issues/55688
Using RefCodeable annotations #
/// define annotation
@RefCodable()
class LoginForm {
var username = '123';
String password;

Info info = Info(nickName: '', gender: 'man');

LoginForm(this.username, this.password);

}

@RefCodable()
class Info {
String nickName;
String gender;

Info({required this.nickName, required this.gender});

Map<String, dynamic> toJson() => {
'nickName': nickName,
'gender': gender,
};

}

/// call ref
var loginForm = LoginForm('username', 'man');
var ref = loginForm.info.nickNameRef;

var nickName = ref.value; // get value
ref.value = '123'; // set value

var node = Binding.mount(context); // old dependentExecutor dispose

var binding = dataRef(node); // dataRef to binding
var binding = loginForm.info.nickNameRef(node); // ref to binding

var nickName = binding.value; // get value but no bind
binding.value = '123'; // set value but do not update page
binding.bindChange(); // get value and add dependentExecutor
binding.notifyChange('123'); // set value and update page

loginForm.info.nickNameRef(node).bindChange(); // get value and add dependentExecutor
loginForm.info.nickNameRef(node).notifyChange('123'); // set value and update page
loginForm.info.nickNameRef(node).value // get value but no bind
loginForm.info.nickNameRef(node).value = '123'; // set value but do not update page

var nickName = loginForm.info.nickNameRef.bindChange(node: node) // get value and add dependentExecutor
loginForm.Info.nickNameRef.notifyChange(node: node, value: '123'); // set value and update page

bindChangeNotifier(node: node, ref: loginForm.info.nickName, changeNotifier: changeNotifier, notifyListener: notifyListener, onChange: onChange);
bindValueNotifier(node: node, ref: loginForm.info.nickName, valueNotifier: valueNotifier);
copied to clipboard

@RefCodable()can be defined in either class or field, and the annotated class or property will automatically generate corresponding reference properties.
Reference attribute name, add the suffix Ref to the field name, for example, the reference attribute of nickNameRef is nickNameRef.

AutoBinding advantages #

Field level comparison: More precise triggering, smaller view refresh range, and higher performance.


The traditional comparison method is object level comparison, and the general approach is to compare the entire object
in the ChangeNotify. addListener() or didChangeDependencies() function. Changes will only trigger the corresponding view
refresh;
However, complex objects cannot achieve recursive multi-level attributes, resulting in inaccurate comparison results.
Another approach is to overload the equal sign operator or rewrite the hashCode. Although this reduces the complexity,
in practical business, sometimes we want its comparison rules to be multiple in different situations. In this case, you
can only write another class with almost the same structure to have different comparison rules.
Moreover, overloading operators makes it difficult to introduce variables other than class members for comparison. So
when the object itself is not sufficient for comparison, a lot of bloated code will be generated for comparison and
debugging purposes.
Even more deadly is that if there are two different types of data, but they are actually the same, an additional
modification synchronization mechanism for the two sets of data needs to be established.


The characteristic of field level comparison is to only compare fields that generate trigger events, and skip data
that does not trigger events even if changes occur.
Moreover, field level comparison does not depend on whether the objects to which the field belongs are equal, and can
even be compared with different types.
Field comparison can also easily introduce external variables to participate in calculation rules. In principle, the
rules for field values are the rules for field comparison;


Field level binding: establishes independent dependency relationships for each field to achieve bidirectional
synchronization between the field and the view.


Traditional event triggering, because it is impossible to determine how much data this change will affect the view, is
usually a large-scale repeated refresh of the page.
Field level binding will collect views with dependencies for each field. After all comparisons are completed, the
system will refresh the view based on the dependencies of the changed fields.
In principle, which views reference a field can determine which views depend on its updates.
Of course, if the view is modified, it will also be written back from the page to the field through dependency
relationships.
Due to the analysis of value changes and dependencies within the system, the annoying setState () no longer needs to be
used. In fact, data changes may affect multiple views, and the value of setState () for a single view is not high, and
it is not recommended to use it.


Progressive development reduces refactoring


We have been thinking about a question, whether a Flutter project can be fully planned from the beginning of project
creation, which data is shared, and which functions will be called.
We believe that the vast majority of projects should evolve from simple to complex processes, even if state management
has been established, new data may be merged and migrated in at any time.
So we believe that reducing the cost of setup and migration is very important


What steps do we need to go through to transition from a regular page to a regular state management framework

Need to extract shared data to create a new Model class and delete local data on the page;
Then, in order for the Model to collect comparative data, all read and write operations on shared data scattered on
the page are extracted into the Model class and made into functions, deleting local code fragments on the page and
converting them into function calls;
Finally, it is necessary to consider which widgets need to retain data during full refresh and may need to be
rewritten as StatefulWidget
The reason why this step occurs is that some callbacks may be directly called and completed now, but because
centralized delay processing is required (object level comparison cannot execute notifyListeners() every time a data
change is made, it must be concentrated until the end), there will be additional data parameters at the restore site,
and how to preserve the data at the restore site
Traditional state management provides manual invocation of updates, with developers making their own decisions on when
to invoke them But if multiple data changes are not in the same place, you actually don't know the last refresh call So
it's refreshing every time, but it's very inefficient



Let's take a look at how AutoBinding reduces renovation costs?

Firstly, the data provider of AutoBinding is designed to be just a regular data object There is no need for a
ChangNotify (provider's solution) with triggering capability, nor does it require additional inheritance from
GetxController (GetX's solution)

The data provider for AutoBinding only needs to be a State or StatelessWidget, and from this point on, the Widget where
the original data owner is located can be directly used for data sharing;

Secondly, we oppose selecting functions with high page relevance as data providers simply because controlling refresh
in data providers would reduce the number of times

Our refresh mechanism is ensured by the dependency relationship of field binding, rather than developers calling
notifyListeners() on their own (in reality, it only shifts the problem onto the developers);
Due to field level dependencies, delayed/asynchronous scenes are essentially preserved Since the site has been
preserved, there is no need to maintain additional status So just keep the function calls as they are
Finally, we need to create a series of field level Ref references and Binding binding objects, and replace all local
values with values from the Binding object
From the perspective of local pages, Binding is like downloading a copy of shared data for local use
After completing the above steps, status management can start working, isn't it very simple?


Subsequent maintenance of AutoBinding setup:

Consider selecting functions with high reusability almost as they are from data providers, only considering
reusability;
Consider migrating local fields to data providers, only considering reusability;


If you think our framework is good, like/email for communication.

License

For personal and professional use. You cannot resell or redistribute these repositories in their original state.

Files In This Product:

Customer Reviews

There are no reviews.