Reputation: 882
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.
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
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
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
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