How to Use GlobalKey to Maintain Widgets States When Changing Parents?
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 global key. So in this article, we have been through How to Use GlobalKey to Maintain Widgets States When Changing Parents.
How to Use GlobalKey to Maintain Widgets States When Changing Parents?
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 the child is Stateful Widget, toggling foo will make a child lose its state, which is usually unexpected.
With such code, you’ll quickly notice that if the child is stateful, toggling foo will make a child lose its state, which is usually unexpected.
To solve this, we’d make our widget stateful, create a GlobalKey, and wrap the child into a KeyedSubtree. Code Snippet will look like the below:
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; } }
Global keys can be used to access the state of a stateful widget from anywhere in the widget tree. Consider a code snippet like the below:
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), ), ], ), ), ); } }
We will get output like a below:
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.
We will get output like a below:
How to:
So, if you want the same widget. Those are 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); }, ) ], ); } }
Conclusion:
Thanks for being with us on a Flutter Journey !!!
So in this article, We have been through how to Use GlobalKey to Maintain Widgets States When Changing Parents.
Keep Learning !!! Keep Fluttering !!!
Flutter Agency is our portal Platform dedicated to Flutter Technology and Flutter Developers. The portal is full of cool resources from Flutter like Flutter Widget Guide, Flutter Projects, Code libs and etc.
Flutter Agency is one of the most popular online portals dedicated to Flutter Technology and daily thousands of unique visitors come to this portal to enhance their knowledge of Flutter.
Contemporary ventures
Recent blog
ready to get started?
Fill out the form below and we will be in touch soon!
"*" indicates required fields