George Lee
George Lee

Reputation: 882

Flutter: How Should I Use an InheritedWidget?

Introduction: I believe that this problem in principle might be shared, but I feel that this specific application is not. I humbly submit it for consideration, so please be gentle. I have stumbled into a clear trap here with my code, and I simply don't know how to fix it. After thinking about it for hours, I don't even have an inkling of what to do.

Problem: The InheritedWidget is not updating the state in the widget above it.

Expectation: I would expect that when I swipe on the PageView of the HomePage, the Text of the BottomAppBar will update. However, this does not happen.

Description: I am trying to use an InheritedWidget (InheritedPageIndex), which I initialised above the root widget (MaterialApp) via a Provider method (please see the code below). Which, has a value that passes down to the widget below it (MenuPage) in the widget tree. I want this value to update when the state changes; which I attempt modifying by swiping a PageView (HomePage) in the widget below it in the Widget tree.

enter image description here

Code: I have re-written and simplified my code so we can more easily diagnose and fix the problem, it looks like this:

page_index_model.dart

import 'package:flutter/material.dart';

    class PageIndex {
      PageIndex({@required this.index});
      double index = 0;
    }

page_index_provider.dart

import 'package:flutter/cupertino.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';

class InheritedPageIndex extends InheritedWidget {
  InheritedPageIndex({Key key, @required this.indexData, Widget child})
      : super(key: key, child: child);

  final PageIndex indexData;

  @override
  bool updateShouldNotify(InheritedWidget oldWidget) => true;

  static PageIndex of(BuildContext context) => context
      .dependOnInheritedWidgetOfExactType<InheritedPageIndex>()
      .indexData;
}

main.dart

import 'package:flutter/material.dart';
import 'package:testinginheritedwidget/models/page_index_model.dart';
import 'package:testinginheritedwidget/pages/menu_page.dart';
import 'package:testinginheritedwidget/providers/page_index_provider.dart';

    void main() => runApp(App());

    class App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return InheritedPageIndex(
          indexData: PageIndex(index: 0),
          child: MaterialApp(
            debugShowCheckedModeBanner: false,
            title: 'Testing Inherited Widgets',
            home: MenuPage(),
          ),
        );
      }
    }

menu_page.dart

    import 'package:flutter/material.dart';
    import 'package:testinginheritedwidget/models/page_index_model.dart';
    import 'package:testinginheritedwidget/pages/home_page.dart';
    import 'package:testinginheritedwidget/providers/page_index_provider.dart';

    class MenuPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        PageIndex _pageIndex = InheritedPageIndex.of(context);

        return DefaultTabController(
          length: 3,
          child: Scaffold(
            //TODO: dot indicator
            resizeToAvoidBottomInset: false,
            appBar: AppBar(
              backgroundColor: Colors.black,
              title: Center(
                child: Text('Testing Inherited Widgets',
                    style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
              ),
              bottom: TabBar(
                onTap: (tabIndex) {},
                labelColor: Colors.white,
                indicatorColor: Colors.white,
                tabs: const <Widget>[
                  Tab(icon: Icon(Icons.flight), child: Text('Flight')),
                  Tab(icon: Icon(Icons.map), child: Text('Map')),
                  Tab(icon: Icon(Icons.print), child: Text('Print')),
                ],
              ),
            ),
            body: SafeArea(
              child: TabBarView(
                children: <Widget>[
                  HomePage(),
                  HomePage(),
                  HomePage(),
                ],
              ),
            ),
            bottomNavigationBar: BottomAppBar(
              child: Row(
                mainAxisAlignment: MainAxisAlignment.center,
                children: <Widget>[
// *** PROBLEM 1 IS HERE ***
                  Text('${_pageIndex.index}'),
                ],
              ),
            ),
          ),
        );
      }
    }

home_page.dart

    import 'package:flutter/material.dart';
    import 'package:testinginheritedwidget/models/page_index_model.dart';
    import 'package:testinginheritedwidget/providers/page_index_provider.dart';

    class HomePage extends StatefulWidget {
      @override
      _HomePageState createState() => _HomePageState();
    }

    class _HomePageState extends State<HomePage> {
      List<Widget> cards = [];

      @override
      Widget build(BuildContext context) {
        PageIndex _pageIndex = InheritedPageIndex.of(context);

        for (var index = 0; index < 7; index++) {
          cards.add(
            Center(
              child: Text(
                '$index',
                style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
              ),
            ),
          );
          print(index);
        }
        return PageView(
          onPageChanged: (index) {
            setState(() {
//*** PROBLEM 2 IS HERE ***
              _pageIndex.index = index.toDouble();
              print('page index: ${_pageIndex.index}');
            });
          },
          children: cards,
        );
      }
    }

Assumption: After much research and reflection. I believe that I cannot update the value of the Provider; because it is between the first instantiated InheritedWidget and the method that changes the state. Maybe another way of saying it is that the State that I want to change is directly above it; and, it instead takes the second Provider's state instead of using the first's?

Questions:

1) For my benefit, am I correct in my assumption, or is there something else going on here?

2) How can I fix my problem? I don't believe that implementing a BLoC method would solve this because it too uses a provider?

I don't know how I would pass the state up the widget tree in this case without the InheritedWidget.

Upvotes: 2

Views: 3977

Answers (3)

RegularGuy
RegularGuy

Reputation: 3676

You don't need a package at all for this. A little hacky way of doing it is creating a Wrapper around inheritedWidget, since inherited widget doens't rebuild by itself, you need to use it inside a stateful widget.

class MyWrapper extends StatefulWidget {
  const MyWrapper({this.child});
  final Widget child;
  @override
  _MyWrapperState createState() => _MyWrapperState();
}

class _MyWrapperState extends State<MyWrapper> {
  int index = 0;

  changePageIndex(int newIndex) {
    setState(() {
      index = newIndex;
    });
  }

  @override
  Widget build(BuildContext context) {
    return InheritedPageIndex(indexData: index , child: widget.child, state: this);
  }
}

class InheritedPageIndex extends InheritedWidget {
  InheritedPageIndex(
      {Key key, @required this.indexData, @required this.state, Widget child})
      : super(key: key, child: child);
  final _MyWrapperState state;
  final int indexData;

  @override
  bool updateShouldNotify(InheritedPageIndex oldWidget) =>
      indexData != oldWidget.indexData;

  static InheritedPageIndex of(BuildContext context) =>
      context.dependOnInheritedWidgetOfExactType<InheritedPageIndex>();
}

Then use it like this

MyWrapper(
  child: MaterialApp(
    debugShowCheckedModeBanner: false,
    title: 'Testing Inherited Widgets',
    home: MenuPage(),
  ),
);

And finally accessing it / changing it like this

int indexData = InheritedPageIndex.of(context).indexData; //we access the variable

InheritedPageIndex.of(context).state.changePageIndex(1); //We change it, and all the widgets that accessed the data with InheritedPageIndex.of(context).indexData rebuild themselves

Upvotes: 3

George Lee
George Lee

Reputation: 882

A Solution With Packages

Introduction: For the benefit of thoroughness, I have included the code as the final answer which uses the Provider package, as suggested in the comments above. This code, as you would expect, is simpler than the above answer which uses no packages.

The code:

main.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stackoverflowquestionthree/counter_model.dart';
import 'package:stackoverflowquestionthree/page_menu.dart';

void main() => runApp(App());

class App extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      title: 'Testing Inherited Widgets',
      home: ChangeNotifierProvider(
        create: (BuildContext context) => Counter(0),
        child: MenuPage(),
      ),
    );
  }
}

counter_model.dart

import 'package:flutter/material.dart';

class Counter with ChangeNotifier {
  int _counter;

  Counter(this._counter);

  getCounter() => _counter;

  setCounter(int counter) => _counter = counter;

  void increment(int counter) {
    _counter = counter;
    notifyListeners();
  }
}

page_menu.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stackoverflowquestionthree/counter_model.dart';
import 'package:stackoverflowquestionthree/home_page.dart';

class MenuPage extends StatefulWidget {
  @override
  _MenuPageState createState() => _MenuPageState();
}

class _MenuPageState extends State<MenuPage> {
  var _bloc;

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    return DefaultTabController(
      length: 3,
      child: Scaffold(
        //TODO: dot indicator
        resizeToAvoidBottomInset: false,
        appBar: AppBar(
          backgroundColor: Colors.black,
          title: Center(
            child: Text('Testing Inherited Widgets',
                style: TextStyle(fontSize: 20.0, fontWeight: FontWeight.bold)),
          ),
          bottom: TabBar(
            onTap: (tabIndex) {
              counter.increment(0);
            },
            labelColor: Colors.white,
            indicatorColor: Colors.white,
            tabs: const <Widget>[
              Tab(icon: Icon(Icons.flight), child: Text('Flight')),
              Tab(icon: Icon(Icons.map), child: Text('Map')),
              Tab(icon: Icon(Icons.print), child: Text('Print')),
            ],
          ),
        ),
        body: SafeArea(
          child: TabBarView(
            children: <Widget>[
              HomePage(),
              HomePage(),
              HomePage(),
            ],
          ),
        ),
        bottomNavigationBar: BottomAppBar(
          child: Row(
            mainAxisAlignment: MainAxisAlignment.center,
            children: <Widget>[
              //TODO: insert value below.
              Text(counter.getCounter().toString()),
            ],
          ),
        ),
      ),
    );
  }
}

home_page.dart

import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:stackoverflowquestionthree/counter_model.dart';

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  List<Widget> cards = [];

  @override
  Widget build(BuildContext context) {
    final counter = Provider.of<Counter>(context);

    for (var index = 0; index < 7; index++) {
      cards.add(
        Center(
          child: Text(
            '$index',
            style: TextStyle(fontSize: 25, fontWeight: FontWeight.bold),
          ),
        ),
      );
      print(index);
    }
    return PageView(
      onPageChanged: (index) {
        setState(() {
          counter.increment(index);
        });
      },
      children: cards,
    );
  }
}

Upvotes: 1

woprandi
woprandi

Reputation: 767

You should check this package : https://pub.dev/packages/provider which is wrappers of InheritedWidget. And check the ChangeNotiferProvider which expose a ChangeNotifier class and allow to notify listeners when updated. I'm available if you need more explanation

Upvotes: 1

Related Questions