Understanding Flutter Keys: When and Why You Need Them
Understanding Flutter Keys: When and Why You Need Them
What You’ll Learn
Keys are Flutter’s way of preserving state when widgets move around in the widget tree. Most of the time you don’t need them, but understanding when to use keys can solve tricky bugs where your widgets lose their state unexpectedly.
Why Keys Matter
Flutter uses keys to match widgets in the old widget tree with widgets in the new tree during rebuilds. Without keys, Flutter relies on widget type and position. This works fine until you reorder, add, or remove stateful widgets—then you’ll see state appearing in the wrong place or disappearing entirely.
Common scenario: You have a list of items that users can reorder. Without keys, the widgets keep their state but swap their data, leading to visual bugs.
Types of Keys
ValueKey: Use when each widget has a unique value (like an ID) ObjectKey: Use when the entire object identifies the widget UniqueKey: Use when you need a guaranteed unique key GlobalKey: Use when you need to access a widget’s state from anywhere (use sparingly)
Example
Here’s a practical example showing the difference:
class TodoItem extends StatefulWidget {
final String title;
// Add a key parameter to preserve state
const TodoItem({
required this.title,
Key? key,
}) : super(key: key);
@override
State<TodoItem> createState() => _TodoItemState();
}
class _TodoItemState extends State<TodoItem> {
bool _isDone = false;
@override
Widget build(BuildContext context) {
return CheckboxListTile(
title: Text(widget.title),
value: _isDone,
onChanged: (value) => setState(() => _isDone = value ?? false),
);
}
}
// Using the TodoItem WITH keys when items can be reordered
class TodoList extends StatefulWidget {
@override
State<TodoList> createState() => _TodoListState();
}
class _TodoListState extends State<TodoList> {
List<String> todos = ['Buy milk', 'Write code', 'Exercise'];
@override
Widget build(BuildContext context) {
return ReorderableListView(
onReorder: (oldIndex, newIndex) {
setState(() {
if (newIndex > oldIndex) newIndex--;
final item = todos.removeAt(oldIndex);
todos.insert(newIndex, item);
});
},
children: [
for (final todo in todos)
// ValueKey ensures checkbox state follows the correct item
TodoItem(
key: ValueKey(todo),
title: todo,
),
],
);
}
}
What’s happening: When you reorder items, the ValueKey tells Flutter “this widget represents ‘Buy milk’, keep its state with it.” Without the key, Flutter would keep the checkbox state in position 1, even though a different todo moved there.
When to Use Keys
- Reorderable lists: Always use keys when items can change position
- Collections of stateful widgets: When adding/removing items from a list of widgets with state
- Siblings of the same type: When you have multiple widgets of the same type side by side and need to preserve their state
When NOT to Use Keys
- Stateless widgets that rebuild from scratch each time
- When widget order never changes
- When widgets don’t maintain internal state
- In most everyday Flutter development (they’re needed less often than you’d think)
Try It Yourself
Create a simple app with a list of colored containers that maintain a color selection state. Add a shuffle button that randomly reorders them. First build it without keys and observe how the colors don’t follow their containers. Then add ValueKey to each container and see the difference.
Tip of the Day
When debugging state issues in lists, add temporary keys to isolate whether the problem is key-related. If adding ValueKey(index) fixes it temporarily, you know you need proper keys based on item identity, not position. Also remember: keys must be unique among siblings, but don’t need to be globally unique (unless using GlobalKey).