In Flutter, keys are identifiers for widgets, elements, and semantics nodes. They are used to preserve the state of widgets when they move around the widget tree. There are different types of keys in Flutter: GlobalKey, UniqueKey, ObjectKey, ValueKey, and PageStorageKey. Let’s explore them, one by one, in this comprehensive article.
Table of Contents
Global Key
A global key is unique across the entire app. It can be used to access the state of a widget from anywhere or to move a widget across the widget tree while preserving its state.
Global keys are usually used for form validation, animations, navigation, and dialogs. To create a global key, just use the constructor of the GlobalKey class as follows:
final myGlobalKey = GlobalKey();
The example below shows you how to use use a global key to access the state of a form widget and validate its fields:
// Define a global key for the form widget
final _formKey = GlobalKey<FormState>();
// Use the global key in the form widget
Form(
key: _formKey,
child: Column(
children: [
TextFormField(
validator: (value) {
if (value == null || value.isEmpty) {
return 'Please enter some text';
}
return null;
},
),
ElevatedButton(
onPressed: () {
// Use the global key to access the form state and validate it
if (_formKey.currentState!.validate()) {
// Do something with the form data
}
},
child: Text('Submit'),
),
],
),
);
Unique Key
A unique key, as its name implies, is unique among its siblings. It can be used to force Flutter to rebuild a widget when its child changes.
You can create a unique key by using the UniqueKey class like this:
UniqueKey()
A unique key can be useful to preserve the state of a stateful widget if it moves around in the widget tree. For example, if you have two stateful widgets that swap their positions in a row, you can use unique keys to keep their colors consistent. Here is a small example that demonstrates this:
import 'package:flutter/material.dart';
import 'dart:math';
void main() => runApp(const MaterialApp(home: PositionedTiles()));
class PositionedTiles extends StatefulWidget {
const PositionedTiles({super.key});
@override
State<StatefulWidget> createState() => PositionedTilesState();
}
class PositionedTilesState extends State<PositionedTiles> {
late List<Widget> tiles;
@override
void initState() {
super.initState();
tiles = [
StatefulColorfulTile(key: UniqueKey()),
StatefulColorfulTile(key: UniqueKey()),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("GEEKSFORGEEKS"),
backgroundColor: Colors.green,
),
body: SafeArea(child: Row(children: tiles)),
floatingActionButton: FloatingActionButton(
onPressed: swapTiles,
child: const Icon(Icons.sentiment_very_satisfied)),
);
}
swapTiles() {
setState(() {
tiles.insert(1, tiles.removeAt(0));
});
}
}
class StatefulColorfulTile extends StatefulWidget {
StatefulColorfulTile({Key? key}) : super(key: key);
final Color myColor = UniqueColorGenerator.getColor();
@override
State<StatefulColorfulTile> createState() => _StatefulColorfulTileState();
}
class _StatefulColorfulTileState extends State<StatefulColorfulTile> {
@override
Widget build(BuildContext context) {
return Container(
color: widget.myColor,
child: const Padding(padding: EdgeInsets.all(70.0)));
}
}
class UniqueColorGenerator {
static List colorOptions = [
Colors.blue,
Colors.red,
Colors.green,
Colors.yellow,
Colors.purple,
Colors.orange,
Colors.indigo,
Colors.amber,
Colors.black,
];
static Random random = Random();
static Color getColor() {
if (colorOptions.isNotEmpty) {
return colorOptions.removeAt(random.nextInt(colorOptions.length));
} else {
return Color.fromARGB(random.nextInt(256), random.nextInt(256),
random.nextInt(256), random.nextInt(256));
}
}
}
Value Key
A value key is a type of key that is based on a constant value that is unique for each widget. It is used to compare widgets based on their values. A value key can be created by using the ValueKey() constructor with any value of a particular type:
ValueKey('some value');
Value keys are often used in Lists, Grids, or reorderable widgets. For example, if you have a list of items that can be deleted or reordered, you can use value keys to keep track of which item is which:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: ListDemo()));
class ListDemo extends StatefulWidget {
const ListDemo({super.key});
@override
State<StatefulWidget> createState() => ListDemoState();
}
class ListDemoState extends State<ListDemo> {
late List<String> items;
@override
void initState() {
super.initState();
items = ['Apple', 'Banana', 'Cherry', 'Date'];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("List Demo"),
),
body: ReorderableListView(
children: items
.map((item) => ListTile(
key: ValueKey(item),
title: Text(item),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => setState(() => items.remove(item)),
),
))
.toList(),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
),
);
}
}
Object Key
An object key is a type of key that is based on an object reference that is unique for each widget. It is used to compare widgets based on their identities. An object key can be created by using the ObjectKey() constructor with any object as its value:
ObjectKey(someObject)
An object can be useful to preserve the state of a stateful widget if it has a unique object associated with it. For example, if you have a list of items that are represented by objects, you can use object keys to keep track of which item is which:
import 'package:flutter/material.dart';
// define Item class
class Item {
final String name;
final Color color;
Item(this.name, this.color);
}
void main() => runApp(const MaterialApp(home: ListDemo()));
class ListDemo extends StatefulWidget {
const ListDemo({super.key});
@override
State<StatefulWidget> createState() => ListDemoState();
}
class ListDemoState extends State<ListDemo> {
late List<Item> items;
@override
void initState() {
super.initState();
items = [
Item('Apple', Colors.red),
Item('Banana', Colors.yellow),
Item('Cherry', Colors.pink),
Item('Date', Colors.brown),
];
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("KindaCode.com"),
),
body: ReorderableListView(
children: items
.map((item) => ListTile(
key: ObjectKey(item),
title: Text(item.name),
trailing: IconButton(
icon: const Icon(Icons.delete),
onPressed: () => setState(() => items.remove(item)),
),
))
.toList(),
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1;
}
final item = items.removeAt(oldIndex);
items.insert(newIndex, item);
});
},
),
);
}
}
Output:
PageStorage Key
A page storage key is a key that can be used to persist the widget state in storage after being destroyed and restored when recreated. It can be used to save and restore values that can outlive the widget, such as the scroll position of a scrollable widget.
You can create page storage key by using the PageStorageKey class, which is a subclass of ValueKey and takes any value of a particular type as its parameter:
PageStorageKey('some key')
PageStorageKey(42)
PageStorageKey(someObject)
A page storage key can be useful to maintain the scroll state of a widget when it is recreated or moved around in the widget tree. For example, if you have a tab view with multiple pages that have scrollable widgets, you can use page storage keys to keep their scroll positions when switching between tabs:
import 'package:flutter/material.dart';
void main() => runApp(const MaterialApp(home: TabViewDemo()));
class TabViewDemo extends StatelessWidget {
const TabViewDemo({super.key});
@override
Widget build(BuildContext context) {
return DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: const Text("KindaCode.com"),
bottom: const TabBar(
tabs: [
Tab(icon: Icon(Icons.home)),
Tab(icon: Icon(Icons.star)),
Tab(icon: Icon(Icons.settings)),
],
),
),
body: TabBarView(
children: [
ListView.builder(
key: const PageStorageKey('home'),
itemCount: 100,
itemBuilder: (context, index) => ListTile(
title: Text("Home item $index"),
),
),
ListView.builder(
key: const PageStorageKey('star'),
itemCount: 100,
itemBuilder: (context, index) => ListTile(
title: Text("Star item $index"),
),
),
ListView.builder(
key: const PageStorageKey('settings'),
itemCount: 100,
itemBuilder: (context, index) => ListTile(
title: Text("Settings item $index"),
),
),
],
),
),
);
}
}
Output:
Conclusion
This is a quite long article. Through it, you’ve learned the most important things about keys in Flutter, including GlobalKey, UniqueKey, ValueKey, ObjectKey, and PageStorageKey. If you’d like to explore more new and interesting stuff in the world of modern Flutter development, take a look at the following articles:
- Flutter AnimatedList – Tutorial and Examples
- Flutter: FilteringTextInputFormatter Examples
- Flutter: Making a Tic Tac Toe Game from Scratch
- 4 Ways to Create Full-Width Buttons in Flutter
- Flutter: Creating a Fullscreen Modal with Search Form
- Flutter: 2 Ways to Create an Onboarding Flow (Intro Slider)
You can also tour around our Flutter topic page or Dart topic page for the most recent tutorials and examples.