Normally, flutter will not use Global Keys. When you create a stateful widget, two object get created: a widget, and it’s state. The idea is that the widget itself will be destroyed at the end of the build (or after it is painted on the screen). Once you initiate the build again (through setState() for exmaple) or state management package– a widget will be recreated.
Of course – you want your state object to persist between the changes – this is where you store your data after all. And you want Flutter to link back your state object to newly created Widget object instance.
Most of the time, this is an easy thing for Flutter – it will just find the same widget in the same position, link it to it’s state object and it’s done.
In case your widget moves around in the next build – this will not work, and new state object will be created and you will lose your state.
Take a look at this modified Flutter Counter app – I moved the counter into a separate Widget, but also linked it back to the the original _MyHomePageState. Each time we rebuild the main widget we will flip the order of the button and text box in the column – and you will see that the count never changes: it always show zero.
Since the widget changed the position, it’s state is dropped and new one created.
import 'package:flutter/material.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
debugShowCheckedModeBanner: false,
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(title: 'Flutter Demo Home Page'),
);
}
}
class MyHomePage extends StatefulWidget {
final String title;
const MyHomePage({
Key? key,
required this.title,
}) : super(key: key);
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
var _key=GlobalKey();
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text(widget.title),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
if (_counter.isOdd) Counter(onPressed: _incrementCounter),
const Text(
'You have pushed the button this many times:',
),
if (_counter.isEven) Counter(onPressed: _incrementCounter),
],
),
),
);
}
}
class Counter extends StatefulWidget {
final VoidCallback onPressed;
const Counter({Key? key, required this.onPressed}) : super(key: key);
@override
_CounterState createState() => _CounterState();
}
class _CounterState extends State<Counter> {
int _counter = 0;
void _incrementCounter() {
setState(() {
_counter++;
});
widget.onPressed();
}
@override
Widget build(BuildContext context) {
return Column(children: [
Text(
'$_counter',
style: Theme.of(context).textTheme.headline4,
),
ElevatedButton(
style:
ElevatedButton.styleFrom(textStyle: const TextStyle(fontSize: 20)),
onPressed: _incrementCounter,
child: const Text('Increment'),
)
]);
}
}
Now simply by adding a GlobalKey to Counter widget (in both calls), we help flutter link the state object. Note that we created the _key value once in the state object – since we want to pass the same key each time (otherwise what’s the point…)
Counter(onPressed: _incrementCounter, key: _key),
And after this change, even when Widget changes the place and order on screen – your state is kept.
Now you would ask: why wouldn’t Flutter do this by default for all the Widgets? Turns out – GlobalKeys are very expensive to maintain, and if you want to render 60 frames per second (16ms each frame) – you want to optimize every single step. By ‘no keys by default’ approach, everything is optimized, and works in large majority of the cases – but in few situations when you need global keys – you need to learn a little bit about it, and it is very simple to use.
Second use I found for it: you need global key to find the your exact Widget position on screen after it renders. Widget itself will not know where it will end up, you only know this after the rendering is done. You need this if you want to do custom animation or something like that.
Rect getRectFromKey(GlobalKey key) {
RenderBox renderBox = (key.currentContext!.findRenderObject())! as RenderBox;
var targetPosition = renderBox.localToGlobal(Offset.zero);
var targetSize = renderBox.size;
// A Rect can be created with one its constructors or from an Offset and a Size using the & operator:
Rect rect = targetPosition & targetSize;
return rect;
}