Understanding Flutter Keys: When and Why to Use Them
Understanding Flutter Keys: When and Why to Use Them
What You’ll Learn
Keys are Flutter’s mechanism for preserving state when the widget tree changes. Most of the time you don’t need them, but when you do, they’re essential for preventing bugs that seem mysterious.
Why Keys Matter
Flutter rebuilds the widget tree constantly. When it does, it uses an algorithm to determine which widgets changed. Sometimes Flutter can’t tell the difference between widgets—especially in lists—and you’ll see state get mixed up or lost.
Keys tell Flutter: “This specific widget instance matters. Track it.”
When You Need Keys
You need keys when:
- Reordering list items with state (like checkboxes or text fields)
- Moving widgets between parent widgets
- Having multiple widgets of the same type at the same level
You DON’T need keys for:
- Static lists that never change
- Widgets that rebuild completely each time
- Single child widgets
Example: The Bug 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]); // BUG: No key!
},
);
}
}
class TodoItem extends StatefulWidget {
final String todo;
const 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: (val) => setState(() => isChecked = val!),
);
}
}
The Problem: If you remove an item from the list, the checkboxes get mixed up! Flutter matches widgets by type and position, not content.
The Fix: Add Keys
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(
key: ValueKey(todos[index]), // FIXED: Unique key per item
todo: todos[index],
);
},
);
}
}
Now Flutter tracks each TodoItem by its unique content, not just its position.
Types of Keys
ValueKey: Use when your data has a unique identifier (strings, numbers)
ValueKey(user.id)
ValueKey('unique_string')
ObjectKey: Use when the whole object is unique
ObjectKey(userObject)
UniqueKey: Generate a unique key (use sparingly—creates new key every build!)
UniqueKey() // New key each time!
GlobalKey: Access widget state from anywhere (use rarely—breaks encapsulation)
final _formKey = GlobalKey<FormState>();
Try It Yourself
Create a simple app with a list of colored containers. Add a button that shuffles the list. First, try it WITHOUT keys and watch the colors stay in position while the list reorders. Then add ValueKey to each container and see them move correctly.
children: colors.map((color) =>
Container(
key: ValueKey(color),
height: 50,
color: color,
)
).toList()
Tip of the Day
Use keys sparingly. Most Flutter widgets don’t need keys. Only add them when you encounter state bugs with lists or moving widgets. If you’re adding keys “just in case,” you probably don’t need them. Let Flutter’s diffing algorithm do its job—it’s very good at it.
The general rule: If your widget has state AND can change position, it probably needs a key.