InheritedWidget confusion
Asked Answered
B

1

6

The Flutter documentation for InheritedWidget says

Base class for widgets that efficiently propagate information down the tree.

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.

Given that widgets in Flutter are immutable, and in the example code..

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  }) : assert(color != null),
       assert(child != null),
       super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor);
  }

  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}

the color property is final so cannot be reassigned. Assuming this widget is right at the top of the tree, as in most examples, when will this ever be useful. For the widget to be replaced, a new instance will have to be created.

Presumably where this is done, a new instance of whatever is passed as child will be created too, causing that child's descendants to also rebuild, creating new instances of its childresn etc..

Ending up with the whole tree rebuilt anyway. So the selective updating applied by using inheritFromWidgetOfExactType is pointless, when the data of an instance of InheritedWidget will never change for that instance?

Edit:

This is the simplest example of what I don't understand that I can put together. In this example, the only way to "change" the InheritedWidget/FrogColor which is near the root of the application is to have its parent (MyApp) rebuild. This causes it to rebuild its children and create a new instance of FrogColor and which gets passed a new child instance. I don't see any other way that the InheritedWidget/FrogColor would change its state as in the documentation

... will cause the consumer to rebuild when the inherited widget itself changes state.

import 'package:flutter/material.dart';
import 'dart:math';

void main() {

  runApp(MyApp());
}

class FrogColor extends InheritedWidget {
  const FrogColor({
    Key key,
    @required this.color,
    @required Widget child,
  }) : assert(color != null),
        assert(child != null),
        super(key: key, child: child);

  final Color color;

  static FrogColor of(BuildContext context) {
    return context.inheritFromWidgetOfExactType(FrogColor);
  }

  @override
  bool updateShouldNotify(FrogColor old) => color != old.color;
}

class MyApp extends StatefulWidget {
  // This widget is the root of your application.

  MyAppState createState() => MyAppState();
}

class MyAppState extends State<MyApp>
{
  @override
  Widget build(BuildContext context) {
    var random = Random(DateTime.now().millisecondsSinceEpoch);

    return FrogColor(
        color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
        child:MaterialApp(
            title: 'Flutter Demo',
            home: Column (
                children: <Widget>[
                  WidgetA(),
                  Widget1(),
                  FlatButton(
                      child:Text("set state",style:TextStyle(color:Colors.white)),
                      onPressed:() => this.setState((){})
                  )
                ]
            )
        )
    );
  }
}

class WidgetA extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Ran Build ${this.runtimeType.toString()}");
    return  WidgetB();
  }
}
class WidgetB extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Ran Build ${this.runtimeType.toString()}");
    return  Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
  }
}
class Widget1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Ran Build ${this.runtimeType.toString()}");
    return  Widget2();
  }
}
class Widget2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    print("Ran Build ${this.runtimeType.toString()}");
    return  Text("SomeText",style:TextStyle(color:FrogColor.of(context).color));
  }
}

Further, the output of this is

I/flutter (24881): Ran Build WidgetA
I/flutter (24881): Ran Build WidgetB
I/flutter (24881): Ran Build Widget1
I/flutter (24881): Ran Build Widget2

So all the child widgets are always rebuilt. Making the registration done in inheritFromWidgetOfExactType pointless also.

Edit2:

In response to @RémiRousselet answer in the comments, modifying the above example, something like

class MyAppState extends State<MyApp>
{
  Widget child;

  MyAppState()
  {
    child = MaterialApp(
        title: 'Flutter Demo',
        home: Column (
            children: <Widget>[
              WidgetA(),
              Widget1(),
              FlatButton(
                  child:Text("set state",style:TextStyle(color:Colors.white)),
                  onPressed:() => this.setState((){})
              )
            ]
        )
    );
  }

  @override
  Widget build(BuildContext context) {
    var random = Random(DateTime.now().millisecondsSinceEpoch);
    return FrogColor(
        color : Color.fromARGB(255,random.nextInt(255),random.nextInt(255),random.nextInt(255)),
        child: child
    );
  }
}

works by storing the tree that shouldn't be modified outside of the build function so that the same child tree is passed to the InhertedWidget on each rebuild. This does work only causing the rebuild of the widgets that have registered with inheritFromWidgetOfExactType to get rebuilt, but not the others.

Although @RémiRousselet says it is incorrect to store the subtree as part of the state, I do not believe there is any reason that this is not ok, and infact they do this in some google tutorial videos. Here She has a subtree created and held as part of the state. In her case 2 StatelessColorfulTile() widgets.

Bulldozer answered 2/2, 2019 at 15:16 Comment(4)
also see scoped_model which is also custom InheritedWidget without that whole boilerplate codeNovia
Possible duplicate of Flutter: How to correctly use an Inherited Widget?With
@George This other answer doesn't really get to the bottom of what I am asking.Bulldozer
Quick bug note: n = nextInt(max) returns 0 <= n < max. Note that max is exclusive, so you should use nextInt(256) to get the range of 0...255. As written in the OP's code, it will return the range of 0...254. api.flutter.dev/flutter/dart-math/Random/nextInt.htmlBruiser
T
2

Presumably where this is done, a new instance of whatever is passed as a child will be created too, causing that child's descendants to also rebuild, creating new instances of its children etc..

Ending up with the whole tree rebuilt anyway.

That's where your confusion comes from

A widget rebuilding doesn't force its descendants to rebuild.

When a parent rebuild, the framework internally check if newChild == oldChild, in which case the child is not rebuilt.

As such, if the instance of a widget didn't change, or if it overrides operator== then it is possible for a widget to not rebuild when its parent is updated.

This is also one of the reasons why AnimatedBuilder offer a child property:

AnimatedBuilder(
  animation: animation,
  builder: (context, child) {
    return Container(child: child,);
  },
  child: Text('Hello world'),
);

This ensures that when for the whole duration of the animation, child is preserved and therefore not rebuilt. Leading to a much more optimized UI.

Triangulate answered 2/2, 2019 at 16:17 Comment(10)
Thanks @RémiRousselet - i've updated my question with a sample of what I'm not getting. I suppose what I don't understand is the conditions under which an InheritedWidget would change when its at the top of the tree. As if this is only when a new instance is created (because of its immutability) then as this is usually a root build function, all other widgets created there would also get reinstanced, causing a rebuild of them.Bulldozer
I suppose the only time I can think of that a child would not be rebuilt is for a StatefulWidget that held its child tree as a member of its state, and just returned this in its build function? .. is this correct?Bulldozer
Incorrect. Look at the AnimatedBuilder example. Now think that AnimatedBuilder is your StatefulWidget, and Container is your InheritedWidget. This is how you should mutate InheritedWidgets.Decimeter
Great, I see how that works with your example. I have edited my question with a modification to the sample I pasted. Although this is storing the child tree as a member of the state, it does mean its created outside of the build function and thus does not get rebuild unless the parent MyApp gets rebuilt and creates a new state. But does now allow only children that are dependent on the InheritedWidget changing to get rebuilt. I assume storing the child tree as a member of the state like this is ok.Bulldozer
... No. Just do like it's done here #49492360Decimeter
that's not possible with my example. I need access to the MyAppState instance in order to hook up onPressed for the button to the MyAppState intstance setState() function, such that the button causes it to rebuild, and then rebuild the InheritedWidget. The MyAppState instance would not be available in order to store child as a final variable on the MyApp widget as in your link, as at that point the state hasn't been created.Bulldozer
Futher to this, when you say "Incorrect", this is not actually true at all. Storing the subtree on the widget vs storing it on the state makes little difference. In fact, looking at one of googles tutorial videos youtu.be/kn0EOS-ZiIc?t=98 this is exactly what is being done here. She has a subtree created and held as part of the state. In her case 2 StatelessColorfulTile() widgets.Bulldozer
Her case is heavily different. It's her state, not a cache.Decimeter
Practically, there is no difference. It is still a sub tree that is stored on the WidgetState and returned in the build function of that WidgetState. Her case is subtly different at best.Bulldozer
No. You'll have a much harder time maintaining yours than hers. Her widgets are "leaves". They don't take a child. Yours is a huge tree with many dependencies.Decimeter

© 2022 - 2024 — McMap. All rights reserved.