There are four kinds of widgets in Flutter
StatelessWidget
StatefullWidget
RenderObjectWidget
InheritedWidget
Among them, StatelessWidget and StatefulWidget are the most common ones, 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, many beginners may not understand it, but it must be used in some slightly complex projects, so this article introduces the usage of 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.
Under normal circumstances, child widgets cannot perceive changes of parent widgets alone. When parent state changes, all child widgets are rebuilt through their builds;
InheritedWidget can avoid this kind of global creation and realize local sub-widget update:
sub-widget gets from buildContext through BuildContext.inheritFromWidgetOfExactType and listens to the specified type of parent InheritedWidget, and rebuilds following its reconstruction
As shown in the figure above, click the C button, after the State changes, the Text of A can be refreshed separately, and B is not affected
code demo
Next, compare the difference between using or not using InheritedWidget through code:
Click +, after the 0 above changes, the text part in the middle does not change.
traditional implementation
After clicking the button state changes, widgetA, B, 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) {
return Scaffold(
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) {
return RaisedButton(
onPressed: () {
incrementCounter();
},
child: Icon(Icons.add),
);
}
}
Using the Flutter Performance of AndroidStudio, you can see that widgets A, B, and C all participated in the rebuild
Implemented using InheritedWidget
import 'package:flutter/material.dart';
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 {
final HomePageState data;
_MyInheritedWidget({ Key key, Widget child, this.data }) : super(key: key, child: child);
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}
class HomePage extends StatefulWidget {
final Widget child;
const HomePage({Key key, @required this.child}) : super(key: key);
@override
State<StatefulWidget> createState() {
return HomePageState();
}
static HomePageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>().data;
}
return context.findAncestorWidgetOfExactType<_MyInheritedWidget>().data;
// or
// return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>().widget as _MyInheritedWidget).data;
}
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
print('HomePageState before _incrementCounter counter $counter');
setState(() {
counter++;
print('HomePageState counter $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.headline4,
),
);
}
}
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);
return RaisedButton(
onPressed: () {
state?._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
Note: This demo 2022.3.5 has been personally verified, and the flutter sdk version used is 1.22.4
The following is the demo of the latest version of flutter sdk, which supports null-safety
import 'package:flutter/material.dart';
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 {
final HomePageState data;
_MyInheritedWidget({ Key? key, required Widget child, required this.data }) : super(key: key, child: child);
@override
bool updateShouldNotify(_MyInheritedWidget oldWidget) {
return true;
}
}
class HomePage extends StatefulWidget {
final Widget child;
const HomePage({Key? key, required this.child}) : super(key: key);
@override
State<StatefulWidget> createState() {
return HomePageState();
}
static HomePageState? of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>()?.data;
}
return context.findAncestorWidgetOfExactType<_MyInheritedWidget>()?.data;
//or
//return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>().widget as _MyInheritedWidget).data;
}
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
print('HomePageState before _incrementCounter counter $counter');
setState(() {
counter++;
print('HomePageState counter $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.headline4,
),
);
}
}
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);
return RaisedButton(
onPressed: () {
state?._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
It can be seen that when the state changes, widgetB and C are not rebuilt
Key code description
Explain the key classes in the InheritedWidget version
WidgetA、WidgetC
In the traditional version, WidgetA and C pass the state of the parent through the constructor and call back the
InheritedWidget version, which can be obtained by the following static methods
final HomePageState state = HomePage.of(context); // WidgetA
final HomePageState state = HomePage.of(context, rebuild: false); // WidgetC
WidgetC is a Button that needs to get the callback method through the state, but does not need to be refreshed with the state change, so rebuild specifies false
Next, let's take a look at the static method HomePage.of to get the state in detail.
HomePage
static HomePageState of(BuildContext context, {bool rebuild = true}) {
if (rebuild) {
return context.dependOnInheritedWidgetOfExactType<_MyInheritedWidget>().data;
}
return context.findAncestorWidgetOfExactType<_MyInheritedWidget>().data;
// or
// return (context.getElementForInheritedWidgetOfExactType<_MyInheritedWidget>().widget as _MyInheritedWidget).data;
}
HomePage.of is used to find the nearest _MyInheritedWidget through buildContext. Then you can get the state it holds with _MyInheritedWidget.
Several key methods to obtain the upper-level Widget are as follows:
method | description |
---|---|
inheritFromWidgetOfExactType | Get the nearest parent widget of a given type. The widget must be a subclass of InheritedWidget, and register the incoming context with the parent widget. When the parent widget changes, the widget held by this context will be rebuilt to obtain a new one from the widget. value. This is how child registers with InheritedWidget. |
ancestorWidgetOfExactType | It is only used to obtain the nearest parent widget of a given type, and will not rebuild due to changes in the parent widget |
ancestorInheritedElementForWidgetOfExactType | The function is the same as inheritFromWidgetOfExactType, but it will only look for subclasses of InheritedWidget, so you can find the superior Widget with O(1) complexity |
The above method is outdated, the new version of flutter should be:
method | description |
---|---|
dependOnInheritedWidgetOfExactType | Get the nearest parent widget of a given type. The widget must be a subclass of InheritedWidget, and register the incoming context with the parent widget. When the parent widget changes, the widget held by this context will be rebuilt to obtain a new one from the widget. value. This is how child registers with InheritedWidget. |
findAncestorWidgetOfExactType | It is only used to obtain the nearest parent widget of a given type, and will not rebuild due to changes in the parent widget |
getElementForInheritedWidgetOfExactType | The function is the same as findAncestorWidgetOfExactType, but it will only find subclasses of InheritedWidget, so you can find the superior Widget with O(1) complexity |
Therefore, widgetA rebuilds as the parent widget changes, and widgetB does not rebuild
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;
}
}
Inherited from InheritedWidget, so child widgets can be obtained through dependOnInheritedWidgetOfExactType.
updateShouldNotify controls whether the child widget needs to feel its changes. If it returns true, the child widget registered through dependOnInheritedWidgetOfExactType will rebuild following its changes.
The ultimate purpose of the child widget is to obtain the shared parent state, so the state is held here through the data attribute.
Then take a look at this HomePageState
HomePageState
@override
Widget build(BuildContext context) {
return _MyInheritedWidget(
data: this,
child: widget.child,
);
}
The use of _MyInheritedWidget is the key here.
In the traditional writing method, widgets A, B, and C are created directly in the build and returned, so whenever the state changes, the child widgets will be recreated and rebuilt;
In the InheritedWidget version, HomePage keeps the children of the parent widget (TopPage). When the state changes, widgets A, B, and C will not be rebuilt, but will be re-passed to _MyInheritedWidget, and only _MyInheritedWidget will be rebuilt.
TopPage
class TopPage extends StatelessWidget {
@override
Widget build(BuildContext context) {
・・・
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: <Widget>[
WidgetA(), // 子widget的创建移动到这里
WidgetB(),
WidgetC(),
],
),
・・・
}
}
According to the above description, in order to avoid repeated creation and rebuilding of child widgets, move the instantiations of widgets A, B, and C to here
InheritedModel
In the above example, we specify whether the sub-Widget participates in the rebuild by customizing the rebuild parameter. In fact, InheritedModel can also be used to complete this requirement.
InheritedModel inherits from InheritedWidget, and can specify a specific child widget to rebuild through the string key (aspect).
A brief look at the difference in implementation between the InheritedModel version and the InheritedWidget version
@override
HomePageState createState() => HomePageState();
static HomePageState of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;
}
}
Use InheritedModel.inheritFrom to get the widget
class _MyInheritedWidget extends InheritedModel {
@override
bool updateShouldNotifyDependent(_MyInheritedWidget old, Set aspects) {
return aspects.contains('A'); // 当aspect包晗“A”时,通知其rebuild
}
}
Inherit InheritedModel, override updateShouldNotifyDependent
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'A'); // 注册aspect为“A“
class WidgetC extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePageState state = HomePage.of(context, 'C'); // 注册aspect为“C”
As above, because the registered key (aspect) is different, only widgetA will be notified of rebuild
The complete code is as follows:
import 'package:flutter/material.dart';
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 InheritedModel {
final HomePageState data;
_MyInheritedWidget({ Key key, Widget child, this.data }) : super(key: key, child: child);
@override
bool updateShouldNotifyDependent(covariant InheritedModel oldWidget, Set dependencies) {
return dependencies.contains('A'); // 当dependencies包晗“A”时,通知其rebuild
}
@override
bool updateShouldNotify(covariant InheritedWidget oldWidget) {
return true;
}
}
class HomePage extends StatefulWidget {
final Widget child;
const HomePage({Key key, @required this.child}) : super(key: key);
@override
State<StatefulWidget> createState() {
return HomePageState();
}
static HomePageState of(BuildContext context, String aspect) {
return InheritedModel.inheritFrom<_MyInheritedWidget>(context, aspect: aspect).data;
}
}
class HomePageState extends State<HomePage> {
int counter = 0;
void _incrementCounter() {
print('HomePageState before _incrementCounter counter $counter');
setState(() {
counter++;
print('HomePageState counter $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, 'A');
return Center(
child: Text(
'${state?.counter}',
style: Theme.of(context).textTheme.headline4,
),
);
}
}
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, 'C');
print('WidgetC counter ${state.counter}');
return RaisedButton(
onPressed: () {
state?._incrementCounter();
},
child: Icon(Icons.add),
);
}
}
more local refresh
If widgetA is as follows, we want to further control the partial 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"), // 此处不需rebuild
],
);
}
}
If you thoroughly understand the registration mechanism of BuildContext and InheritedWidget, it can be easily implemented:
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"),
],
);
Create an anonymous class widget through Builder, and then move HomePage.of inside it. At this time, the context registered in InheritedWidget is no longer widgetA but this anonymous class widget, so the partial refresh of widgetA can be realized
Not using InheritedWidget
I think through the above introduction, you should be able to think that if the child widget only wants to access the parent state (without passing parameters through the constructor), but there is no need to monitor its changes, you can not use 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; // 持有state供子类获取
@override
HomePageState createState() {
state = HomePageState();
return state;
}
}
class HomePageState extends State<HomePage> {
int counter = 0; // 去掉private
void incrementCounter() { // 去掉private
setState(() {
counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Column(
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
children: [
WidgetA(),
WidgetB(),
WidgetC(),
],
),
);
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
final HomePage widget = context.ancestorWidgetOfExactType(HomePage); // 获取state
final HomePageState 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);
final HomePageState state = widget?.state;
return RaisedButton(
onPressed: () {
state?.incrementCounter();
},
child: Icon(Icons.add),
);
}
}
Find the specified type of widget through ancestorWidgetOfExactType, and then get its state for use. Of course, this traversal is O(n), and the performance is worse than the InheritedWidget version
finally
Many components in Flutter are implemented based on InheritedWidget, such as Scoped Model, BLoC (Business Logic of component), etc. To master the use of these advanced features, start by understanding InheritedWidget
Extension:
Flutter uses InheritedModel to achieve partial refresh
The defined data model is
import 'package:flutter/material.dart';
import 'user_type.dart';
class UserInheritedModel extends InheritedModel<UserType> {
final int age;
final int weight;
const UserInheritedModel(
{required this.age, required this.weight, required Widget child})
: super(child: child);
static UserInheritedModel? of(BuildContext context,
{required UserType aspect}) {
return InheritedModel.inheritFrom<UserInheritedModel>(context,
aspect: aspect);
}
@override
bool updateShouldNotify(UserInheritedModel old) {
return age != old.age || weight != old.weight;
}
@override
bool updateShouldNotifyDependent(
UserInheritedModel old, Set<UserType> aspects) {
return (aspects.contains(UserType.age) && age != old.age) ||
(aspects.contains(UserType.height) && weight != old.weight);
}
}
The page to be partially refreshed is
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
InkWell(
child: const AgePage(
ideaType: UserType.age,
),
onTap: () {
setState(() {
_age += 1;
});
},
),
Divider(),
InkWell(
child: const WeightPage(
ideaType: UserType.height,
),
onTap: () {
setState(() {
_weight += 1;
});
},
),
],
),
Included pages are
class AgePage extends StatelessWidget {
final UserType ideaType;
const AgePage({Key? key, required this.ideaType}) : super(key: key);
@override
Widget build(BuildContext context) {
final UserInheritedModel? _ideasTypeIdea =
UserInheritedModel.of(context, aspect: ideaType);
return Text(
'${_ideasTypeIdea!.age}\n${Random.secure().nextDouble()}',
);
}
}
Another page similar to the above
What is InheritedWidget and How it works in a Flutter??
The above article introduces the implementation principle of InheritedWidget, and the modified demo is posted below for reference.
import 'package:flutter/material.dart';
class MyStatefulWidget extends StatefulWidget {
final Widget child;
const MyStatefulWidget({Key key, @required this.child}) : super(key: key);
static MyStatefulWidgetState of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>().data;
}
@override
State<StatefulWidget> createState() {
return MyStatefulWidgetState();
}
}
class MyStatefulWidgetState extends State<MyStatefulWidget> {
int _counterValue = 0;
int get counterValue => _counterValue;
void addCounterBy() {
setState(() {
_counterValue += 1;
});
}
@override
Widget build(BuildContext context) {
return MyInheritedWidget(
child: widget.child,
data: this,
);
}
}
class MyInheritedWidget extends InheritedWidget {
final MyStatefulWidgetState data;
MyInheritedWidget({
Key key,
@required Widget child,
@required this.data,
}) : super(key: key, child: child);
@override
bool updateShouldNotify(InheritedWidget oldWidget) {
return true;
}
}
class MyContainer extends StatelessWidget {
final Widget child;
MyContainer({
Key key,
@required this.child,
}) : super(key: key);
void onPressed(BuildContext context) {
MyStatefulWidget.of(context).addCounterBy();
}
@override
Widget build(BuildContext context) {
return Center(
child: Container(
width: 200,
height: 200,
child: RaisedButton(
color: Colors.red,
onPressed: (){
onPressed(context);
},
child: child,
),
),
);
}
}
class DummyContainer extends StatelessWidget {
final Widget child;
const DummyContainer({Key key, this.child}) : super(key: key);
@override
Widget build(BuildContext context) {
return child;
}
}
class WidgetA extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text(
"Counter",
style: TextStyle(
color: Colors.white,
fontSize: 20,
),
);
}
}
class WidgetB extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Text('I am a widget that will not be rebuilt.',
style: TextStyle(
fontSize: 14,
color: Colors.white,
),);
}
}
class WidgetC extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return CounterValueState();
}
}
class CounterValueState extends State<WidgetC> {
int counterValue;
double fontSize;
@override
void didChangeDependencies() {
super.didChangeDependencies();
MyStatefulWidgetState data = MyStatefulWidget.of(context);
counterValue = data.counterValue;
fontSize = 50.0 + counterValue;
}
@override
Widget build(BuildContext context) {
return Text(
"$counterValue",
style: TextStyle(
fontSize: fontSize,
color: Colors.white,
),
);
}
}