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…