I'm having trouble accessing instances of objects (or States) in Flutter, from other classes. I've tried a lot of fiddling using similar questions on the web, and am currently using 'GlobalKey', but I just can't get it working.
I'm trying to make a simple Flutter app where the State of a Widget gets accessed from another class, on button press:
import 'viewer.dart' as viewer;
(...)
onPressed: () {
//Works
print("Doing something");
//Doesn't work
viewer.key.currentState.nextPage();
},
My viewer.dart file looks contains a PageController, and a class containing that controller:
final key = new GlobalKey<_RegistryState>();
final PageController _controller = PageController(
initialPage: 0,
);
class Registry extends StatefulWidget {
Registry({ Key key }) : super(key: key);
#override
_RegistryState createState() => _RegistryState();
}
class _RegistryState extends State<Registry> {
void next() {
print("Doing something!");
_controller.nextPage();
}
#override
Widget build(BuildContext context) {
return PageView(
//physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
controller: _controller,
children: [
registry_screens.ScreenSplash(),
registry_screens.ScreenName(),
Text("Bye"),
],
);
}
#override
void dispose() {
_controller.dispose();
super.dispose();
}
}
The idea is that whenever the button gets pressed, the PageController moves to the next page (which is already there, I can scroll to it manually by swiping on the screen).
The app compiles fine, but when pressing the button I get the error 'NoSuchMethodError: invalid member on null: 'next''.
Am I using the correct approach for accessing instances of Widgets (or States)?
Flutter is a declarative framework. In this kind of environment, everytime that you want to change the view (or interface) you need to rebuild it. And if you rebuild what is holding the state, you would loose it. That's why it should not be responsible of holding the state of the program.
State management in Flutter is a broad subject with lots of options. As #DrSatan1 mentioned in the comments, in Flutter.dev you can find good documentation about state management using Provider, but you have lots of options with BLoC, ReduX, MobX, etc.
In your specific scenario, since it is simpler, you could accomplish that using a global object or Inherited Widget.
Global Object
globals.dart
currentPage=0;
In the Widget
import 'globals.dart' as global;
(...)
onPressed: () {
setState((){
globals.currentPage++;
});
},
viewer.dart
#override
Widget build(BuildContext context) {
return PageView(
//physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
currentPage: globals.currentPage, //instead of using PageController
children: [
registry_screens.ScreenSplash(),
registry_screens.ScreenName(),
Text("Bye"),
],
);
}
You could use the PageController as your global object. In that case you could pass the PageController down the widget tree. In this case, it would be better to use InheritedWidget instead.
InheritedWidget
As per docs, InheritedWidget is
Base class for widgets that efficiently propagate information down the
tree.
You can pass your PageController to all the widgets below the tree. Your viewer.dart would be:
(...)
#override
Widget build(BuildContext context) {
return MyInheritedWidget (
pageController: _controller,
child: PageView(
//physics: NeverScrollableScrollPhysics(), //Disable user manually scrolling
//controller: _controller, // Don't pass controller here
children: [
registry_screens.ScreenSplash(),
registry_screens.ScreenName(),
Text("Bye"),
],
);
);
}
(...)
// create the inherited widget wrapper. It could be done with [Builder][7] too, instead of a different Widget.
class MyInheritedWidget extends InheritedWidget {
final PageController pageController;
MyInheritedWidget({
Key key,
#required Widget child,
#required this.pageController,
}) : super(key: key, child: child);
#override
bool updateShouldNotify(InheritedWidget oldWidget) => true;
}
(...)
After that you can access pageController in PageView or any Widget under it.
(...)
onPressed: () {
//Works
print("Doing something");
// Find closest InheritedWidget
MyInheritedWidget myInheritedWidget =
context.dependOnInheritedWidgetOfExactType<MyInheritedWidget>()
// Get pageController from it
PageController controller = myInheritedWidget.pageController
// call nextPage()
nextPage();
},
(...)
Although both methods works in your specific scenario, you should check Flutter Docs about state management. Maybe you don't need the PageController at all.
It's generally a bad idea for state to be accessed externally. Instead, external classes should only interact with Widgets through the methods they expose.
I just made a video walking through the exact same onboarding setup you have using a PageView, which you can see here -- as I go through it step-by-step: https://www.youtube.com/watch?v=ji__FEKSnMw
In essence, it looks like this:
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
// This widget is the root of your application.
#override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
home: MainPage(),
debugShowCheckedModeBanner: false,
);
}
}
class MainPage extends StatefulWidget {
const MainPage({Key key}) : super(key: key);
#override
_MainPageState createState() => _MainPageState();
}
class _MainPageState extends State<MainPage> {
PageController pageController = new PageController(initialPage: 0);
#override
Widget build(BuildContext context) {
return Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: Container(
child: PageView(
controller: pageController,
physics: NeverScrollableScrollPhysics(),
children: [
Slide(
hero: Image.asset("./assets/hero-1.png"),
title: "Boost your traffic",
subtitle:
"Outreach to many social networks to improve your statistics",
onNext: nextPage),
Slide(
hero: Image.asset("./assets/hero-2.png"),
title: "Give the best solution",
subtitle:
"We will give best solution for your business isues",
onNext: nextPage),
Slide(
hero: Image.asset("./assets/hero-3.png"),
title: "Reach the target",
subtitle:
"With our help, it will be easier to achieve your goals",
onNext: nextPage),
Scaffold(
backgroundColor: Colors.white,
body: Center(
child: Text(
'Be kind to yourself',
style: kTitleStyle,
),
),
)
])),
),
);
}
void nextPage() {
pageController.nextPage(
duration: const Duration(milliseconds: 200), curve: Curves.ease);
}
}
class Slide extends StatelessWidget {
final Widget hero;
final String title;
final String subtitle;
final VoidCallback onNext;
const Slide({Key key, this.hero, this.title, this.subtitle, this.onNext})
: super(key: key);
#override
Widget build(BuildContext context) {
return Container(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Expanded(child: hero),
Padding(
padding: const EdgeInsets.all(20),
child: Column(
children: [
Text(
title,
style: kTitleStyle,
),
SizedBox(
height: 20,
),
Text(
subtitle,
style: kSubtitleStyle,
textAlign: TextAlign.center,
),
SizedBox(
height: 35,
),
],
),
),
GestureDetector(
onTap: onNext,
child: Text(
"Skip",
style: kSubtitleStyle,
),
),
SizedBox(
height: 4,
)
],
),
);
}
}
Related
I am using the pull_to_refresh package.
I am having a Stack() with two elements. One of them is the Refresher(). When I pull down on my screen, activating the refreshing animation, the build method is called constantly. The problem is that my second Widget in my Stack is quite complex to build and takes some time. I want to prevent having it build all the time when triggering the Refresher-Animation. Is this possible?
My simplified code would look like this:
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: Stack(children: <Widget>[
SafeArea(
child: Column(children: [
Expanded(
child: Container(
margin: EdgeInsets.all(0),
width: 100.w,
constraints: const BoxConstraints.expand(),
child: SizedBox(
width: 100.w,
child: Refresher( refresher stuff )
)
)
)
)
),
SecondItem()
)
)
}
Somehow the build method of SecondItem is called all the time. Not the build method of the whole scaffold.
If Your second Item dont want to get refresh, then add it as a separeate class like,
Expanded(
child: Container(
margin: EdgeInsets.all(0),
width: 100.w,
constraints: const BoxConstraints.expand(),
child: SizedBox(
width: 100.w,
child: Refresher( refresher stuff )
)
)
)
)
),
SecondItem()
)
class SecondItem extends StatefulWidget {
#override
_SecondItemState createState() => _SecondItemState();
}
class _SecondItemState extends State<SecondItem> {
int counter = 0;
#override
Widget build(BuildContext context) {
return Scaffold(
body: Text("Your Second Widget"),
);
}
}
Now your SecondItem() will not get refresh when you refresh your FirstItem()
Since I wasn't really able to replicate the problem, I build a working structure that implements refreshing.
First the main widget, in my case MyHomePage.
This widget implements the Scaffold and Stack with FirstWidget and SecondWidget as children.
class MyHomePage extends StatefulWidget {
const MyHomePage({
Key? key,
}) : super(key: key);
#override
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
#override
Widget build(BuildContext context) {
return Scaffold(
body: Stack(
children: const <Widget>[
FirstWidget(),
SecondWidget(),
],
),
);
}
}
FirstWidget is a statefull widget with a counter in the state.
It implements the refresher with a specific controller.
Once the refresh is triggered, it calls set state and updates the counter within his state.
That should trigger only his build again and not any other.
I implemented a Text to show the counter value increasing at each refresh, and a Print to expose the build.
class FirstWidget extends StatefulWidget {
const FirstWidget({
Key? key,
}) : super(key: key);
#override
State<FirstWidget> createState() => _FirstWidgetState();
}
class _FirstWidgetState extends State<FirstWidget> {
late int _counter;
late RefreshController _refreshController;
#override
void initState() {
_counter = 1;
_refreshController = RefreshController(initialRefresh: false);
super.initState();
}
#override
Widget build(BuildContext context) {
print('First widget built');
return SafeArea(
child: Column(
children: [
Container(
margin: const EdgeInsets.all(0),
width: double.infinity,
height: 500,
color: Colors.red,
child: SmartRefresher(
controller: _refreshController,
onRefresh: () async {
setState(() {
_counter++;
});
await Future.delayed(const Duration(milliseconds: 1000));
_refreshController.refreshCompleted();
},
),
),
Text("Counter: $_counter"),
],
),
);
}
}
Last we got the SecondWidget which is a another simple widget with a print statement.
In case of build it writes on the console.
When the FirstWidget refresh, the second doesn't build becouse his state has not changed.
class SecondWidget extends StatefulWidget {
const SecondWidget({
Key? key,
}) : super(key: key);
#override
State<SecondWidget> createState() => _SecondWidgetState();
}
class _SecondWidgetState extends State<SecondWidget> {
#override
Widget build(BuildContext context) {
print('Second widget built');
return const Center(child: Text('Second here!'));
}
}
Possible cause of your problem.
It could be that when refreshing, you actually are updating the state of a parent widget that, on cascade, causes the re build of your second widget.
If state is handled correctly, and your second widget doesn't depends on your first widget state, the refresh should not rebuild the second.
There is a red customView and a button in the page:
I want to change the customView's color to green when I tap the button.
Require:
You must call customView's function changeColor to achieve it;
You can't call page's setState, it's stateless;
Do not use eventBus or provider.
Here is all my code, you can copy and test, input your code in CustomView's changeColor, I desire the easiest way.
import 'package:flutter/material.dart';
class RefreshOutsidePage extends StatelessWidget {
const RefreshOutsidePage({Key key}) : super(key: key);
#override
Widget build(BuildContext context) {
CustomView customView = CustomView();
return Scaffold(
appBar: AppBar(title: Text('refresh outside')),
body: Column(
children: [
customView,
SizedBox(height: 30),
RaisedButton(
child: Text('refresh outside'),
onPressed: () {
customView.changeColor();
},
),
],
),
);
}
}
class CustomView extends StatefulWidget {
CustomView({Key key}) : super(key: key);
#override
_CustomViewState createState() => _CustomViewState();
void changeColor() {
// input your code here
print('change');
}
}
class _CustomViewState extends State<CustomView> {
Color color = Colors.red;
void changeColor() {
setState(() {
color = Colors.green;
});
}
#override
Widget build(BuildContext context) {
return Container(
width: 200,
height: 200,
color: color,
);
}
}
You can change it through key:
Make CustomViewState public (remove _ at beginning)
Define key and call function changeColor:
class RefreshOutsidePage extends StatelessWidget {
const RefreshOutsidePage({Key key}) : super(key: key);
final _customViewKey = GlobalKey<CustomViewState>();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('refresh outside')),
body: Column(
children: [
CustomView(key: _customViewKey),
SizedBox(height: 30),
RaisedButton(
child: Text('refresh outside'),
onPressed: () {
_customViewKey.currentState.changeColor();
},
),
],
),
);
}
}
There are two main solutions.
Key solution as mentioned by Autocrab.
State solution where the parent widget becomes Stateful or implement any state management solution in Flutter to update the values of his child.
The child CustomView should be Stateless as it is now because you are not changing the state within widget. So you just require extra parameters received from the parent widget to properly update or get a reference to the widget with the GlobalKey to update it.
If you are using this project for learning or something not legacy I suggest you upgrade Flutter as RaisedButton is deprecated and on the long term you will also have to use null-safety. But that is outside the scope of this question.
I want to hide some widgets when the drawer opens. (it's mean when user open drawer then I need to hide some widgets)
Currently, I am using
if(!_scaffoldKey.currentState.isDrawerOpen)
//hide widget
But this is not listen. Is there any way to do listen drawer callbacks?
There is no callback mechanism till now in flutter which gives events for Drawer(), but still we can apply a good solution for it.
I divided the solution using two stateful widgets,
HomeScreen (Main Widget)
MyDrawer (Drawer Widget)
1. HomeScreen:
class HomeScreen extends StatefulWidget {
#override
_HomeScreenState createState() => _HomeScreenState();
}
class _HomeScreenState extends State<HomeScreen> {
final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>();
bool _isDrawerOpen = false;
void drawerCallback(bool isOpen) {
print('Drawer Status:' + isOpen.toString());
// Based on the bool value set visibility of your widget
WidgetsBinding.instance.addPostFrameCallback((_){
setState(() {
_isDrawerOpen = isOpen;
});
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
appBar: AppBar(title: Text('Nav Sample App')),
body: _isDrawerOpen
? Align(alignment: Alignment.centerRight, child: Text('Drawer Open'))
: Align(alignment: Alignment.centerRight, child: Text('Drawr Close')),
drawer: MyDrawer(drawerCallback));
}
}
Above you can see that based on _isDrawerOpen we are setting widgets in the body with the ternary operator.
2. MyDrawer()
class MyDrawer extends StatefulWidget {
final Function _drawerCallback;
MyDrawer(this._drawerCallback);
#override
_MyDrawerState createState() => _MyDrawerState();
}
class _MyDrawerState extends State<MyDrawer> {
#override
void initState() {
super.initState();
widget._drawerCallback(true);
}
#override
void dispose() {
widget._drawerCallback(false);
super.dispose();
}
#override
Widget build(BuildContext context) {
return Drawer(
child: ListView(
children: <Widget>[
DrawerHeader(
child: Text('Drawer Header'),
decoration: BoxDecoration(color: Colors.blue),
),
Text("Drawer Item 1"),
Text("Drawer Item 2"),
],
),
);
}
}
Heart of the logic is applied in initState() and dispose() callbacks where we are returning status.
You can use the onTap() function for drawers using a ListView:
Drawer(
child: ListView(
// Important: Remove any padding from the ListView.
padding: EdgeInsets.zero,
children: <Widget>[
DrawerHeader(
child: Text('Drawers'),
decoration: BoxDecoration(
color: Colors.blue,
),
),
ListTile(
title: Text('Drawer 1'),
onTap: () {
// Insert code to hide or delete your desired widget
// Then close the drawer
Navigator.pop(context);
},
),
],
),
),//drawer
I'm new to using Flutter/Dart and I'm having a hard time understanding why the following code doesn't update the ListView contents when _updateResults() calls setState(). I've even added a print statement to display the contents of _results and I can see that items are being added to the list but the display never updates.
class SearchHomePage extends StatefulWidget {
SearchHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
createState() => _SearchHomePageState();
}
class _SearchHomePageState extends State<SearchHomePage> {
List<Widget> _results = <Widget>[];
#override
void initState() {
super.initState();
_results.add(Text("testing"));
}
void _updateResults(String text) {
setState(() {
_results.add(Text(text));
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(children: [
Container(
child: TextField(
onSubmitted: (String text) {
_updateResults(text);
},
),
),
Expanded(
child: ListView(children: _results)
),
]),
);
}
}
If I change the ListView portion to be like:
Expanded(
child: ListView(children: _results.toList())
)
Then it will work and I'm not sure why because it was already a list. What exactly is going on here and why doesn't it work with just ListView?
From StatefulWidget documentation:
StatefulWidget instances themselves are immutable and store their mutable state either in separate State objects that are created by the createState method, or in objects to which that State subscribes [...]
From this article:
By definition, immutable means that, once created, an object/variable can’t be changed. So, instead of changing a property of an object, you have to make a copy (or clone) of the entire object and in the process, change the property in question.
You created the ListView with the same array. You changed the content of the array, but you did not change the reference to that object.
That's why it works when you use _results.toList() because .toList() "creates a [List] containing the elements of this [Iterable]".
Another solution could be:
setState(() {
_results = List.from(_results)
..add(Text(text));
});
it doesn't work because you are adding a new item to the existing list and not creating a new list.
this will fix your problem:
class SearchHomePage extends StatefulWidget {
SearchHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
createState() => _SearchHomePageState();
}
class _SearchHomePageState extends State<SearchHomePage> {
List<Widget> _results = <Widget>[];
#override
void initState() {
super.initState();
_results.add(Text("testing"));
}
void _updateResults(String text) {
setState(() {
_results = [..._results, Text(text)];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(children: [
Container(
child: TextField(
onSubmitted: (String text) {
_updateResults(text);
},
),
),
Expanded(
child: ListView(children: _results)
),
]),
);
}
}
note: it's better to do the follwing rather than creating widgets and saving them in an array:
class SearchHomePage extends StatefulWidget {
SearchHomePage({Key key, this.title}) : super(key: key);
final String title;
#override
createState() => _SearchHomePageState();
}
class _SearchHomePageState extends State<SearchHomePage> {
List<String> _results = <String>[];
#override
void initState() {
super.initState();
_results.add("testing");
}
void _updateResults(String text) {
setState(() {
_results = [..._results, text];
});
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Column(children: [
Container(
child: TextField(
onSubmitted: (String text) {
_updateResults(text);
},
),
),
Expanded(
child: ListView(
children: _results.map((v) => Text(v)).toList(),
)),
]),
);
}
}
when you call .toList() it will create a new list instance (copy of your old list)
ListView(
shrinkWrap: true,
children: _results.map((one) => one).toList(),
),
ListView.builder(
shrinkWrap: true,
itemCount: _results.length,
itemBuilder: (context, index) => _results[index],
),
If You strive to getting your output as per your requirement then you have to change your code like above instand of below code:
Expanded(
child: ListView(children: _results.toList()))
Please put any one listview from both...
In Emily Fortuna's article (and video) she mentions:
GlobalKeys have two uses: they allow widgets to change parents
anywhere in your app without losing state, or they can be used to
access information about another widget in a completely different part
of the widget tree. An example of the first scenario might if you
wanted to show the same widget on two different screens, but holding
all the same state, you’d want to use a GlobalKey.
Her article includes a gif demo of an app called "Using GlobalKey to ReuseWidget" but does not provide source code (probably because it's too trivial). You can also see a quick video demo here, starting at 8:30 mark: https://youtu.be/kn0EOS-ZiIc?t=510
How do I implement her demo? Where do I define the GlobalKey variable and how/where do I use it? Basically for example, I want to display a counter that counts up every second, and have it on many different screens. Is that something GlobalKey can help me with?
The most common use-case of using GlobalKey to move a widget around the tree is when conditionally wrapping a "child" into another widget like so:
Widget build(context) {
if (foo) {
return Foo(child: child);
}
return child;
}
With such code, you'll quickly notice that if child is stateful, toggling foo will make child lose its state, which is usually unexpected.
To solve this, we'd make our widget stateful, create a GlobalKey, and wrap child into a KeyedSubtree.
Here's an example:
class Example extends StatefulWidget {
const Example({Key key, this.foo, this.child}) : super(key: key);
final Widget child;
final bool foo;
#override
_ExampleState createState() => _ExampleState();
}
class _ExampleState extends State<Example> {
final key = GlobalKey();
#override
Widget build(BuildContext context) {
final child = KeyedSubtree(key: key, child: widget.child);
if (widget.foo) {
return Foo(child: child);
}
return child;
}
}
I would not recommend using GlobalKey for this task.
You should pass the data around, not the widget, not the widget state. For example, if you want a Switch and a Slider like in the demo, you are better off just pass the actual boolean and double behind those two widgets. For more complex data, you should look into Provider, InheritedWidget or alike.
Things have changed since that video was released. Saed's answer (which I rewarded 50 bounty points) might be how it was done in the video, but it no longer works in recent Flutter versions. Basically right now there is no good way to easily implement the demo using GlobalKey.
But...
If you can guarantee that, the two widgets will never be on the screen at the same time, or more precisely, they will never be simultaneously inserted into the widget tree on the same frame, then you could try to use GlobalKey to have the same widget on different parts of the layout.
Note this is a very strict limitation. For example, when swiping to another screen, there is usually a transition animation where both screens are rendered at the same time. That is not okay. So for this demo, I inserted a "blank page" to prevent that when swiping.
How to:
So, if you want the same widget, appearing on very different screens (that hopefully are far from each other), you can use a GlobalKey to do that, with basically 3 lines of code.
First, declare a variable that you can access from both screens:
final _key = GlobalKey();
Then, in your widget, have a constructor that takes in a key and pass it to the parent class:
Foo(key) : super(key: key);
Lastly, whenever you use the widget, pass the same key variable to it:
return Container(
color: Colors.green[100],
child: Foo(_key),
);
Full Source:
import 'package:flutter/material.dart';
void main() {
runApp(MaterialApp(home: MyApp()));
}
class MyApp extends StatelessWidget {
final _key = GlobalKey();
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("Global Key Demo")),
body: PageView.builder(
itemCount: 3,
itemBuilder: (context, index) {
switch (index) {
case 0:
return Container(
color: Colors.green[100],
child: Foo(_key),
);
break;
case 1:
return Container(
color: Colors.blue[100],
child: Text("Blank Page"),
);
break;
case 2:
return Container(
color: Colors.red[100],
child: Foo(_key),
);
break;
default:
throw "404";
}
},
),
);
}
}
class Foo extends StatefulWidget {
#override
_FooState createState() => _FooState();
Foo(key) : super(key: key);
}
class _FooState extends State<Foo> {
bool _switchValue = false;
double _sliderValue = 0.5;
#override
Widget build(BuildContext context) {
return Column(
children: [
Switch(
value: _switchValue,
onChanged: (v) {
setState(() => _switchValue = v);
},
),
Slider(
value: _sliderValue,
onChanged: (v) {
setState(() => _sliderValue = v);
},
)
],
);
}
}
Update: this was an old approach to tackle the state management and not recommended anymore,please see my comments on this answer and also check user1032613's answer below
Global keys can be used to access the state of a statefull widget from anywhere in the widget tree
import 'package:flutter/material.dart';
main() {
runApp(MaterialApp(
theme: ThemeData(
primarySwatch: Colors.indigo,
),
home: App(),
));
}
class App extends StatefulWidget {
#override
State<App> createState() => _AppState();
}
class _AppState extends State<App> {
GlobalKey<_CounterState> _counterState;
#override
void initState() {
super.initState();
_counterState = GlobalKey();
}
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
children: <Widget>[
Counter(
key: _counterState,
),
],
)),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.navigate_next),
onPressed: () {
Navigator.of(context).push(
MaterialPageRoute(builder: (context) {
return Page1(_counterState);
}),
);
},
),
);
}
}
class Counter extends StatefulWidget {
const Counter({
Key key,
}) : super(key: key);
#override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int count;
#override
void initState() {
super.initState();
count = 0;
}
#override
Widget build(BuildContext context) {
return Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
count++;
});
},
),
Text(count.toString()),
],
);
}
}
class Page1 extends StatefulWidget {
final GlobalKey<_CounterState> counterKey;
Page1( this.counterKey);
#override
_Page1State createState() => _Page1State();
}
class _Page1State extends State<Page1> {
#override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Row(
children: <Widget>[
IconButton(
icon: Icon(Icons.add),
onPressed: () {
setState(() {
widget.counterKey.currentState.count++;
print(widget.counterKey.currentState.count);
});
},
),
Text(
widget.counterKey.currentState.count.toString(),
style: TextStyle(fontSize: 50),
),
],
),
),
);
}
}