This is the fifth day of my participation in the August More text Challenge. For details, see: August More Text Challenge
Managing the Flutter Application State With InheritedWidgets. The website documentation or recommended articles will give you a better understanding of the status management mechanism of Flutter.
preface
Part I Introduction and Combat (41) : You can use a custom InheritedWidget subclass Called ModelBinding to enable child components to access state directly, eliminating the need to pass state along the component tree and reducing the coupling between parent and child components. But there are two problems:
ModelBinding
Classes are not universal; each page needs to define its ownInheritedWidget
Subclasses.- The state change callback is still a component that needs to be passed from the top level to the operational state; the coupling is not completely uncoupled.
This article will modify the previous ModelBinding class to implement a more general, less coupled ModelBinding class.
Decouple the status update callback
The InheritedWidget version simplifies updating the model by binding components to the Model. Now, any subordinate component of the ModelBinding can take the model and update it, so that it does not need to be processed through a callback. As mentioned earlier, through viewModel.of (context), a child of a ModelBinding can get the value of the model, enabling model-dependent components to automatically update with changes to the model. Similarly, a child component of the ModelBinding can update model data via viewModel.update (context, newModel). It looks good!
To enable the static viewModel.update method to update the model, we need to introduce an additional stateful component. This is going to be a little bit more complicated.
- will
ModelBinding
Becomes a stateful component and holds the current state model object. - for
ModelBinding
Built a_ModelBindingScope
的InheritedWidget
A child component that referencesState<ModelBinding>
– that is,_ModelBingingState
“Refers to the parent state. - In order to change
ModelBinding
The value of the current model, thus in the callsetState
Methods reconstructionModelBinding
And rebuild the subordinate_ModelBindingScope
. - through
_ModelBindingScope
To implement theViewModel
Class to obtain model objects and update objects statically
The code looks like this:
static ViewModel of(BuildContext context) {
_ModelBindingScope scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
return scope.modelBindingState.currentModel;
}
static void update(BuildContext context, ViewModel newModel) {
_ModelBindingScope scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
scope.modelBindingState.updateModel(newModel);
}
Copy the code
Now that any child component of the ModelBinding can use these methods to update the model data, the button code below both retries and updates the model data.
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Hello World ${ViewModel.of(context).value}'),
onPressed: () {
ViewModel model = ViewModel.of(context);
ViewModel.update(context, ViewModel(value: model.value + 1)); }); }Copy the code
Run it and it looks fine (full code: model_binding_v1.dart). Should we celebrate? However, if we had hundreds of states we would write dozens or hundreds of ModelBinding classes, and each Model would provide a static of(context) and update method, right?
Build a generic ModelBinding class using generics
To ensure universality of the ModelBinding, we need to use generics to dynamically bind the state model objects. To start with, change the _ModelBIndingScope class:
class _ModelBindingScope<T> extends InheritedWidget {
_ModelBindingScope({
Key key,
@required this.modelBindingState,
Widget child,
}) : assert(modelBindingState ! =null),
super(key: key, child: child);
final _ModelBindingV2State<T> modelBindingState;
@override
bool updateShouldNotify(_ModelBindingScope oldWidget) => true;
}
Copy the code
It’s easy to change, just add generic parameters. Next up is _ModelBindV2State, which may be changed to generic.
class _ModelBindingV2State<T> extends State<ModelBindingV2<T>> {
T currentModel;
@override
void initState() {
super.initState();
currentModel = widget.create();
}
void updateModel(T newModel) {
if (currentModel != newModel) {
setState(() {
currentModel = newModel;
});
}
}
@override
Widget build(BuildContext context) {
return _ModelBindingScope<T>(
modelBindingState: this, child: widget.child, ); }}Copy the code
In fact, it just adds the generic parameter. The one thing to note here is that we built the initial state directly in _ModelBidingV2State. Now, because it’s generic, we can’t build the generic object directly, so we need to get it from the stateful component. Here we call the Create method of the ModeBinding class in initState to return a generic object (or, of course, we can use assignment directly, depending on how the ModelBinding class gets the initial state object).
In this case, we need to pass a generic object fetching method, create, to the constructor. In addition, we need to promote the fetching model object methods and updating methods that were previously placed in the ViewModel to the ModelBinding class. And become a generic method, so that the external only need the ModelBinding class can complete the acquisition and update of the state model object, simplify the implementation of the state model object to the greatest extent.
class ModelBindingV2<T> extends StatefulWidget {
ModelBindingV2({Key key, @required this.create, this.child})
: assert(create ! =null),
super(key: key);
final ValueGetter<T> create;
final Widget child;
@override
_ModelBindingV2State<T> createState() => _ModelBindingV2State<T>();
static T of<T>(BuildContext context) {
_ModelBindingScope<T> scope =
context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope);
return scope.modelBindingState.currentModel;
}
static voidupdate<T>(BuildContext context, T newModel) { _ModelBindingScope<T> scope = context.dependOnInheritedWidgetOfExactType(aspect: _ModelBindingScope); scope.modelBindingState.updateModel(newModel); }}Copy the code
After the modification, our actual Controller code will look like this:
class StateViewControllerV2 extends StatelessWidget {
StateViewControllerV2({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Model Binding Generic Edition'), ), body: Center( child: ModelBindingV2( create: () => ViewModel(), child: ViewController(), ), ), ); }}class ViewController extends StatelessWidget {
const ViewController({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return ElevatedButton(
child: Text('Hello World ${ModelBindingV2.of<ViewModel>(context).value}'),
onPressed: () {
ViewModel model = ModelBindingV2.of<ViewModel>(context);
ModelBindingV2.update(context, ViewModel(value: model.value + 1)); }); }}Copy the code
As you can see, the entire ModelBinding class does the fetching and updating of state and applies to any state model class.
conclusion
This code has been uploaded to: Status Management Example. From the previous article and this one, the core component of state management is actually an InheritedWidget. With the ability to use InheritedWidget to update all the child components that depend on that component’s state when their state changes. Through the use of generics, we have built one of the simplest common state management components. Of course, state management in the real world is much more complex than this, but understanding how it works will help you optimize performance.
I’m an island user with the same name on wechat. This is a column on introduction and practice of Flutter.
👍🏻 : feel a harvest please point to encourage!
🌟 : Collect articles, convenient to read back!
💬 : Comment exchange, mutual progress!