There are four widgets in Flutter
- StatelessWidget
- StatefullWidget
- RenderObjectWidget
- InheritedWidget
Statelesswidgets and StatefulWidgets are the most common, which are classified from the perspective of state management. RenderObjectWidget is the base class for all widgets that need to be rendered.
As for the last InheritedWidget, which many beginners may not know about but are required to use in slightly more complex projects, this article explains how to use the InheritedWidget
InheritedWidget
To obtain the nearest instance of a particular type of inherited widget from a build context, use BuildContext.inheritFromWidgetOfExactType.
Inherited widgets, when referenced in this way, will cause the consumer to rebuild when the inherited widget itself changes state.
In general, the child widgets cannot sense the change of the parent widget alone. When the parent state changes, all the child widgets are rebuilt through their build.
Inheritedwidgets can avoid this global creation and implement local child widget updates: From child widgets by BuildContext. InheritFromWidgetOfExactType BuildContext father InheritedWidget capturing and monitoring of the specified type, and follow the rebuilding and rebuild
As shown in the figure above, click THE C button. After the State changes, A’s Text can be refreshed separately, while B is not affected
Code demo
Here’s a code to compare the difference between using or not using an InheritedWidget:
Click + to change the 0 above, and the text in the middle does not change.
Traditional implementation
When state changes, widgetA, B, and C will rebuild
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo'), ), body: HomePage(), ), ); }}class HomePage extends StatefulWidget {
@override
_HomePageState createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
returnScaffold( body: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ WidgetA(_counter), WidgetB(), WidgetC(_incrementCounter), ], ), ); }}class WidgetA extends StatelessWidget {
final int counter;
WidgetA(this.counter);
@override
Widget build(BuildContext context) {
return Center(
child: Text(
'${counter}', style: Theme.of(context).textTheme.display1, ), ); }}class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.'); }}class WidgetC extends StatelessWidget {
final void Function() incrementCounter;
WidgetC(this.incrementCounter);
@override
Widget build(BuildContext context) {
returnRaisedButton( onPressed: () { incrementCounter(); }, child: Icon(Icons.add), ); }}Copy the code
WidgetA, B, and C are all involved in rebuild using AndroidStudio’s Flutter Performance
Use the InheritedWidget implementation
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: HomePage(
child: Scaffold(
appBar: AppBar(
title: Text('InheritedWidget Demo'), ), body: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: <Widget>[ WidgetA(), WidgetB(), WidgetC(), ], ), ), ), ); }}class _MyInheritedWidget extends InheritedWidget {
_MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final HomePageState data;
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true; }}class HomePage extends StatefulWidget {
HomePage({
Key key,
this.child,
}) : super(key: key);
final Widget child;
@override
HomePageState createState() => HomePageState();
static HomePageState of(BuildContext context, {bool rebuild = true{})if (rebuild) {
return (context.inheritFromWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
}
return (context.ancestorWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
// or
// return (context.ancestorInheritedElementForWidgetOfExactType(_MyInheritedWidget).widget as _MyInheritedWidget).data;}}class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this, child: widget.child, ); }}class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context);
return Center(
child: Text(
'${state.counter}', style: Theme.of(context).textTheme.display1, ), ); }}class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.'); }}class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, rebuild: false);
returnRaisedButton( onPressed: () { state._incrementCounter(); }, child: Icon(Icons.add), ); }}Copy the code
Note widgetB and C have no rebuild when state changes
Key Code Description
Focus on the key classes in the InheritedWidget version
WidgetA, WidgetC
WidgetA, the state that C passes to the parent through the constructor, and the InheritedWidget version with the callback are available in the following static method
final HomePageState state = HomePage.of(context); // WidgetA
final HomePageState state = HomePage.of(context, rebuild: false); // WidgetC
Copy the code
WidgetC is a Button that needs to get the callback method through state, but does not need to refresh with state, so rebuild specifies false
Let’s take a closer look at the static method homepage.of that gets state
HomePage
static HomePageState of(BuildContext context, {bool rebuild = true{})if (rebuild) {
return (context.inheritFromWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
}
return (context.ancestorWidgetOfExactType(_MyInheritedWidget) as _MyInheritedWidget).data;
// or
// return (context.ancestorInheritedElementForWidgetOfExactType(_MyInheritedWidget).widget as _MyInheritedWidget).data;
}
Copy the code
Homepage. of is used to find the nearest _MyInheritedWidget by buildContext. You can then retrieve the state held by the _MyInheritedWidget.
The key ways to get a parent Widget are as follows:
method | description |
---|---|
inheritFromWidgetOfExactType | Gets the most recent parent Widget of a given type, which must be a subclass of the InheritedWidget, and registers the incoming context with the parent Widget. When the parent Widget changes, The widget held by this context is rebuilt to get new values from the widget. This is how child registers with the InheritedWidget. |
inheritFromWidgetOfExactType | It is only used to get the most recent parent Widget of a given type, and will not be rebuilt because of changes to the parent Widget |
ancestorInheritedElementForWidgetOfExactType | Functions like inheritFromWidgetOfExactType, but will only find InheritedWidget subclass, so can look for the superior to O (1) the complexity of the Widget |
Therefore, widgetA rebuilds as the parent widget changes, widgetB does not rebuild
_MyInheritedWidget
class _MyInheritedWidget extends InheritedWidget {
_MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
final HomePageState data;
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true; }}Copy the code
Inherited from InheritedWidget, so the child widgets can be obtained through inheritFromWidgetOfExactType.
UpdateShouldNotify control whether you need the child widgets to feel the change, if you return true, by rebuild inheritFromWidgetOfExactType registered child widgets to follow the change
The child widget’s ultimate goal is to get the parent state of the share, so state is held here through the data property.
Let’s take a look at the HomePageState
HomePageState
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
Copy the code
Here the use of _MyInheritedWidget is key.
In traditional writing, widgetA, B, and C are created directly in build and returned, so child widgets are rebuilt and rebuilt whenever state changes.
In the InheritedWidget version, a HomePage keeps the children of the parent widget (TopPage). When the state changes, widgetA, B, and C do not rebuild the HomePage, but repass it to the _MyInheritedWidget. Only the _MyInheritedWidget is rebuilt
TopPage
class TopPage extends StatelessWidget {
@overrideWidget build (BuildContext context) {... the body: the Column (mainAxisAlignment: mainAxisAlignment spaceEvenly, children: The < widgets > [WidgetA (),// The creation of the child widget is moved hereWidgetB(), WidgetC(),],),...}}Copy the code
As explained above, to avoid repeated creation and rebuild of child widgets, move the instantiations of widgetA, B, and C here
InheritedModel
In the example above, we specify whether the child Widget participates in the rebuild by customizing the rebuild parameter. In fact, we can also use the InheritedModel to fulfill this requirement
InheritedModel inherits the InheritedWidget, which can be rebuilt by specifying a particular child widget with a string key (aspect).
Take a quick look at the implementation differences between the InheritedModel version and the InheritedWidget version
@override
HomePageState createState() => HomePageState();
static HomePageState of(BuildContext context, String aspect) {
returnInheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data; }}Copy the code
Using InheritedModel inheritFrom for widgets
class _MyInheritedWidget extends InheritedModel {
@override
bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) {
return aspects.contains('A'); // Notify the rebuild when the aspect wraps "A"}}Copy the code
Inheriting InheritedModel, rewrite updateShouldNotifyDependent
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'A'); // Register aspect as "A"
Copy the code
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'C'); // Register aspect as "C"
Copy the code
As shown above, only widgetA will be notified of the rebuild because the registered key(aspect) is different
A more local refresh
If widgetA looks like this, we would like to have further control over the local refresh of its child widgets
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context);
return Column(
children: <Widget>[
Center(
child: Text(
'${state.counter}',
style: Theme.of(context).textTheme.display1,
),
),
Text("AAAAA"), // No rebuild is required]); }}Copy the code
This can be easily implemented if we thoroughly understand the BuildContext and InheritedWidget registration mechanisms:
return Column(
children: <Widget>[
Center(
child: Builder(builder: (context){
final HomePageState state = HomePage.of(context);
return Text(
'${state.counter}',
style: Theme.of(context).textTheme.display1,
);
}),
),
Text("AAAAA"),]);Copy the code
Use the Builder to create an anonymous class widget and move homepage.of inside it. At this point, the context registered in the InheritedWidget is no longer widgetA but the anonymous class widget, so local refresh of widgetA can be implemented
Do not use InheritedWidget
If a child InheritedWidget only wants to access its parent state (without passing an argument through a constructor) and doesn’t need to listen for changes, you can use an InheritedWidget without using the InheritedWidget:
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: Scaffold(
appBar: AppBar(
title: Text('Demo'), ), body: HomePage(), ), ); }}class HomePage extends StatefulWidget {
HomePageState state; // Hold state for subclasses to get
@override
HomePageState createState() {
state = HomePageState();
returnstate; }}class HomePageState extends State<HomePage> {
int counter = 0; / / remove private
void incrementCounter() { / / remove private
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
returnScaffold( body: Column( mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [ WidgetA(), WidgetB(), WidgetC(), ], ), ); }}class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage); / / for the state
finalHomePageState state = widget? .state;return Center(
child: Text(
'${state == null ? 0 : state.counter}', style: Theme.of(context).textTheme.display1, ), ); }}class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.'); }}class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage);
finalHomePageState state = widget? .state;return RaisedButton(
onPressed: () {
state?.incrementCounter();
},
child: Icon(Icons.add),
);
}
}
Copy the code
Through ancestorWidgetOfExactType looking for a specified type of widget, then get the state of use, of course the traversal is O (n), performance is worse than InheritedWidget version
FIN
Many of the Flutter components are implemented based on inheritedWidgets, such as Scoped Model, BLoC(Business Logic of Component), etc. To master the use of these advanced features, start by understanding the inheritedWidgets
Code: github.com/vitaviva/fl…