Mastering ValueKey, ObjectKey, and GlobalKey in Flutter
Mastering ValueKey, ObjectKey, and GlobalKey in Flutter
What You’ll Learn
Keys in Flutter are powerful tools that help the framework identify and preserve widget state, especially when widgets move around in the tree. Today, you’ll understand when and how to use ValueKey, ObjectKey, and GlobalKey to prevent unexpected state loss and bugs.
Why Keys Matter
Without keys, Flutter identifies widgets by their type and position. When you reorder, add, or remove widgets, Flutter might associate state with the wrong widget. Keys solve this by giving widgets unique identities.
Example: The Problem Without Keys
class TodoList extends StatefulWidget {
@override
_TodoListState createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
List<String> todos = ['Buy milk', 'Walk dog', 'Write code'];
@override
Widget build(BuildContext context) {
return ListView.builder(
itemCount: todos.length,
itemBuilder: (context, index) {
return TodoItem(todo: todos[index]);
},
);
}
}
class TodoItem extends StatefulWidget {
final String todo;
TodoItem({required this.todo});
@override
_TodoItemState createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> {
bool isChecked = false;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(widget.todo),
value: isChecked,
onChanged: (value) => setState(() => isChecked = value!),
);
}
}
If you remove an item from the middle of the list, the checkbox states get misaligned because Flutter can’t track which widget is which.
The Solution: Using ValueKey
class _TodoListState extends State<TodoList> {
List<String> todos = ['Buy milk', 'Walk dog', 'Write code'];
@override
Widget build(BuildContext context) {
return Column(
children: [
...todos.map((todo) => TodoItem(
key: ValueKey(todo), // Add ValueKey based on todo text
todo: todo,
)),
ElevatedButton(
onPressed: () {
setState(() => todos.removeAt(1));
},
child: Text('Remove middle item'),
),
],
);
}
}
Now each TodoItem has a unique identifier based on its content, and Flutter preserves the correct state when reordering.
When to Use Each Key Type
ValueKey: Use when your data has a unique primitive value (String, int, etc.)
ValueKey('unique-id-123')
ValueKey(userId)
ObjectKey: Use when your data is a complex object
class Todo {
final int id;
final String title;
Todo(this.id, this.title);
}
ObjectKey(todoObject)
GlobalKey: Use when you need to access a widget’s state or context from outside its subtree
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
Form(
key: _formKey,
child: // form fields
)
// Later, you can access form state:
_formKey.currentState?.validate();
Try It Yourself
Create a simple reorderable list of colored containers. Without keys, add the ability to remove items and notice how colors get mixed up. Then add ValueKey based on the color value and observe how it fixes the issue.
Challenge: Build a form with multiple TextFormField widgets. Use GlobalKey to programmatically focus on specific fields and validate them from a button outside the form.
Tip of the Day
Performance consideration: Don’t overuse GlobalKey. They’re more expensive than local keys because Flutter needs to maintain a registry of all GlobalKeys. Use ValueKey or ObjectKey whenever possible, and reserve GlobalKey for cases where you truly need cross-tree access to widget state.
Also remember: Keys should be stable. Don’t generate new keys on every build (like using Key(Random().nextInt(1000))) as this defeats their purpose!