0

There are some confusions about InheritedWidget that I don't understand.
I have searched and read some QAs about InheritedWidget on stackoverflow, but there are still things that I don't understand.
First of all, let's create a scenario.
This is my InheritedWidget:

class MyInheritedWidget extends InheritedWidget {
  final String name;
  MyInheritedWidget({
    @required this.name,
    @required Widget child,
    Key key,
  }) : super(key: key, child: child);

  @override
  bool updateShouldNotify(MyInheritedWidget oldWidget) =>
      oldWidget.name != this.name;

  static MyInheritedWidget of(BuildContext context) {
    return context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>();
  }
}

and this is MyHomePage that contains the MyInheritedWidget. MyInheritedWidget has two children: WidgetA and a button that navigates to another screen, in this case Page1.

class MyHomePage extends StatefulWidget {
  @override
  State createState() => new MyHomePageState();
}

class MyHomePageState extends State<MyHomePage> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: MyInheritedWidget(
          name: 'Name',
          child: Column(
            children: [
              WidgetA(),
              TextButton(
                onPressed: () {
                  Navigator.of(context).push(
                    MaterialPageRoute(
                      builder: (context) => Page1(),
                    ),
                  );
                },
                child: Text('Go to page 1'),
              )
            ],
          ),
        ),
      ),
    );
  }
}

Inside WidgetA there is a text widget that displays the name field from MyInheritedWidget and another button that navigates to Page2.

class WidgetA extends StatefulWidget {
  @override
  _WidgetAState createState() => _WidgetAState();
}

class _WidgetAState extends State<WidgetA> {
  @override
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    return Column(
      children: [
        Text(myInheritedWidget.name),
        TextButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => Page2(),
              ),
            );
          },
          child: Text('Go to page 2'),
        )
      ],
    );
  }
}

Page1 and Page2 each has only a text widget that displays the name field from MyInheritedWidget.

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 1'),
      ),
      body: Text(myInheritedWidget.name),
    );
  }
}
class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final myInheritedWidget = MyInheritedWidget.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Page 2'),
      ),
      body: Text(myInheritedWidget.name),
    );
  }
}

In this scenario, the name field of MyInheritedWidget is not accessible form Page1 and Page2, but it can be accessed in WidgetA.

Now lets get to the question:
It is said that an InheritedWidget can be accessed from all of its descendants. What does descendant mean?
In MyHomePage, I know WidgetA is a descendant of MyInheritedWidget. but, is Page1 also a descendant of MyInheritedWidget?
If the answer is no, How can I make Page1 a descendant of MyInheritedWidget?
Do I need to wrap it again inside MyInheritedWidget?
What if there is a chain of navigations like this: Page1-> Page2 -> Page3 ... Page10 and I want to access MyInheritedWidget in Page10, Do I have to wrap each of the pages inside MyInheritedWidget?

Ehsan Askari
  • 843
  • 7
  • 19
  • 1
    *"[...] but, is Page1 also a descendant of MyInheritedWidget?"* - no `Page1` is not a descendant of `MyInheritedWidget` - `Page1` is a descendant of main `Navigator` – pskink Mar 31 '21 at 06:58

1 Answers1

1

As @pskink says, MyHomePage pushes Page1, which is a descendant of Navigator, which is under MaterialApp, not MyInheritedWidget. The easiest solution is to create MyInheritedWidget above MaterialApp. This is my code (using ChangeNotifierProvider instead of MyInheritedWidget).

void main() {
  setupLocator();
  runApp(DevConnectorApp());
}

class DevConnectorApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final log = getLogger('DevConnectorApp');
    return MultiProvider(
      providers: [
        ChangeNotifierProvider<AuthService>(
          create: (ctx) => AuthService(),
        ),
        ChangeNotifierProxyProvider<AuthService, ProfileService>(
          create: (ctx) => ProfileService(),
          update: (ctx, authService, profileService) =>
              profileService..updateAuth(authService),
        ),
      ],
      child: Consumer<AuthService>(builder: (ctx, authService, _) {
        log.v('building MaterialApp with isAuth=${authService.isAuth}');
        return MaterialApp(

Here is an example using multiple Navigators to scope the InheritedWidget. The widget ContextWidget creates an InheritedWidget and a Navigator and has child widgets for the screens in your example.

class InheritedWidgetTest extends StatefulWidget {
  @override
  State createState() => new InheritedWidgetTestState();
}

class InheritedWidgetTestState extends State<InheritedWidgetTest> {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Home'),
      ),
      body: Center(
        child: Column(
          children: [
            ContextWidget('First'),
            ContextWidget('Second'),
          ],
        ),
      ),
    );
  }
}

class ContextWidget extends StatelessWidget {
  Navigator _getNavigator(BuildContext context, Widget child) {
    return new Navigator(
      onGenerateRoute: (RouteSettings settings) {
        return new MaterialPageRoute(builder: (context) {
          return child;
        });
      },
    );
  }

  final name;
  ContextWidget(this.name);
  @override
  Widget build(BuildContext context) {
    return MyInheritedWidget(
      name: this.name,
      child: Expanded(
        child: _getNavigator(
          context,
          PageWidget(),
        ),
      ),
    );
  }
}

class PageWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        WidgetA(),
        TextButton(
          onPressed: () {
            Navigator.of(context).push(
              MaterialPageRoute(
                builder: (context) => Page1(),
              ),
            );
          },
          child: Text('Go to page 1'),
        )
      ],
    );
  }
}
Patrick O'Hara
  • 1,999
  • 1
  • 14
  • 18
  • *"The easiest solution is to create MyInheritedWidget above MaterialApp"* - i think its better to use [MaterialApp.builder](https://api.flutter.dev/flutter/material/MaterialApp/builder.html) but i agree, the result is the same – pskink Mar 31 '21 at 07:25
  • @Patrick I know that wrapping the `MaterialApp` inside `MyInheritedWidget` makes it accessible globally. But I want it to be accessible to only to some part of my app. – Ehsan Askari Mar 31 '21 at 08:47
  • I guess you could make additional `Navigators` for the different parts of your app. See https://stackoverflow.com/questions/46502751/how-to-use-multiple-navigators for a discussion of that approach, though it is not about and does not mention `InheritedWidget`. – Patrick O'Hara Mar 31 '21 at 09:52
  • I added example code for the multiple `Navigators`. – Patrick O'Hara Mar 31 '21 at 10:42