Keys
The problem keys solve
Section titled “The problem keys solve”Recall reconciliation: when rebuilding, Flutter matches each new widget to the existing element by position and type. Usually that is exactly right. But in a list of same-typed stateful widgets, position-matching breaks when items move.
Imagine two TodoItem widgets (each stateful, each holding, say, a checkbox state). Swap their order in the list. Flutter sees “position 0 is still a TodoItem, position 1 is still a TodoItem” — types and positions match — so it keeps each element (and its state) exactly where it was. The data moved but the state did not. Result: you reorder the list and the checkboxes stay put on the wrong rows.
flowchart LR
subgraph before["Before swap"]
a0["item A (checked)"] --- a1["item B"]
end
subgraph after["After swap, NO keys"]
b0["item B
but still shows checked"] --- b1["item A
now unchecked"]
end
before -->|state stuck to position| after Keys give an element an identity
Section titled “Keys give an element an identity”A key tells Flutter “this element’s identity is X, not just its position.” Now, when matching new widgets to elements, Flutter matches by key — so state follows the widget even when it moves.
// Give each item a stable key derived from its data:return ListView( children: [ for (final todo in todos) TodoItem(key: ValueKey(todo.id), todo: todo), ],);With ValueKey(todo.id), swapping the list reorders the elements to match — each TodoItem’s state travels with its id. The rule of thumb: you need keys when you have a list of stateful widgets whose order or membership can change. Stateless lists rarely need them.
The four kinds of key
Section titled “The four kinds of key”| Key | Identity based on | Use when |
|---|---|---|
ValueKey(v) | a value (id, string) | Most list items — a stable data value identifies each |
ObjectKey(o) | object identity | The identifying thing is an object, not a simple value |
UniqueKey() | always unique | You want to force a new element (discard old state) every build |
GlobalKey() | app-wide unique | You need to access a widget state or its context from elsewhere (use sparingly) |
GlobalKey is the heavy one: it uniquely identifies a widget across the entire app and lets you reach its State (e.g. formKey.currentState!.validate()). It is powerful but expensive and easy to misuse — prefer local keys unless you truly need cross-tree access.