Answer
Different Types of Keys in Flutter
Keys in Flutter help the framework identify which widgets have changed, been added, or removed. They are crucial for maintaining state and optimizing widget tree updates.
Key Types Overview
| Key Type | Use Case | Example |
|---|---|---|
| ValueKey | Unique primitive values | ValueKey(1) |
| ObjectKey | Complex objects | ObjectKey(user) |
| UniqueKey | Always unique | UniqueKey() |
| GlobalKey | Access widget state/context | GlobalKey() |
| PageStorageKey | Preserve scroll position | PageStorageKey('list') |
1. ValueKey
Used when widgets can be uniquely identified by a single value (int, string, etc.).
dartclass ValueKeyExample extends StatelessWidget { final List<String> items = ['A', 'B', 'C']; Widget build(BuildContext context) { return Column( children: items.map((item) => Container( key: ValueKey(item), // Unique string identifier child: Text(item), )).toList(), ); } } // Real-world example: Reorderable list class TodoList extends StatefulWidget { _TodoListState createState() => _TodoListState(); } class _TodoListState extends State<TodoList> { List<String> todos = ['Buy milk', 'Walk dog', 'Write code']; Widget build(BuildContext context) { return ReorderableListView( onReorder: (oldIndex, newIndex) { setState(() { if (newIndex > oldIndex) newIndex--; final item = todos.removeAt(oldIndex); todos.insert(newIndex, item); }); }, children: todos.map((todo) => ListTile( key: ValueKey(todo), // Preserves state during reorder title: Text(todo), )).toList(), ); } }
2. ObjectKey
Used when you need to identify widgets by complex objects.
dartclass User { final int id; final String name; User(this.id, this.name); bool operator ==(Object other) => identical(this, other) || other is User && runtimeType == other.runtimeType && id == other.id; int get hashCode => id.hashCode; } class ObjectKeyExample extends StatelessWidget { final List<User> users = [ User(1, 'Alice'), User(2, 'Bob'), User(3, 'Charlie'), ]; Widget build(BuildContext context) { return Column( children: users.map((user) => UserWidget( key: ObjectKey(user), // Uses entire object for identity user: user, )).toList(), ); } } class UserWidget extends StatefulWidget { final User user; const UserWidget({Key? key, required this.user}) : super(key: key); _UserWidgetState createState() => _UserWidgetState(); } class _UserWidgetState extends State<UserWidget> { bool isExpanded = false; Widget build(BuildContext context) { return ExpansionTile( title: Text(widget.user.name), initiallyExpanded: isExpanded, onExpansionChanged: (value) => setState(() => isExpanded = value), ); } }
3. UniqueKey
Generates a new unique key every time. Use sparingly as it causes rebuilds.
dartclass UniqueKeyExample extends StatefulWidget { _UniqueKeyExampleState createState() => _UniqueKeyExampleState(); } class _UniqueKeyExampleState extends State<UniqueKeyExample> { List<Widget> items = []; void addRandomItem() { setState(() { items.add(Container( key: UniqueKey(), // Forces new widget every time height: 50, color: Colors.primaries[items.length % Colors.primaries.length], )); }); } Widget build(BuildContext context) { return Column( children: [ ...items, ElevatedButton( onPressed: addRandomItem, child: Text('Add Item'), ), ], ); } } // Use case: Force widget rebuild class RefreshableWidget extends StatefulWidget { _RefreshableWidgetState createState() => _RefreshableWidgetState(); } class _RefreshableWidgetState extends State<RefreshableWidget> { Key _key = UniqueKey(); void refresh() { setState(() { _key = UniqueKey(); // Forces complete rebuild }); } Widget build(BuildContext context) { return Column( children: [ ExpensiveWidget(key: _key), ElevatedButton( onPressed: refresh, child: Text('Refresh'), ), ], ); } }
4. GlobalKey
Provides access to the widget's state and context from anywhere.
dartclass GlobalKeyExample extends StatefulWidget { _GlobalKeyExampleState createState() => _GlobalKeyExampleState(); } class _GlobalKeyExampleState extends State<GlobalKeyExample> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<ScaffoldState> _scaffoldKey = GlobalKey<ScaffoldState>(); Widget build(BuildContext context) { return Scaffold( key: _scaffoldKey, body: Form( key: _formKey, child: Column( children: [ TextFormField( validator: (value) { if (value == null || value.isEmpty) { return 'Please enter text'; } return null; }, ), ElevatedButton( onPressed: () { // Access form state from anywhere if (_formKey.currentState!.validate()) { ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Valid!')), ); } }, child: Text('Submit'), ), ], ), ), ); } } // Access widget size and position class MeasurementExample extends StatefulWidget { _MeasurementExampleState createState() => _MeasurementExampleState(); } class _MeasurementExampleState extends State<MeasurementExample> { final GlobalKey _containerKey = GlobalKey(); Size? _size; Offset? _position; void measureWidget() { final RenderBox renderBox = _containerKey.currentContext!.findRenderObject() as RenderBox; setState(() { _size = renderBox.size; _position = renderBox.localToGlobal(Offset.zero); }); } Widget build(BuildContext context) { return Column( children: [ Container( key: _containerKey, width: 200, height: 100, color: Colors.blue, ), ElevatedButton( onPressed: measureWidget, child: Text('Measure'), ), if (_size != null) Text('Size: ${_size!.width} x ${_size!.height}'), if (_position != null) Text('Position: ${_position!.dx}, ${_position!.dy}'), ], ); } }
5. PageStorageKey
Preserves scroll positions and other page state across navigation.
dartclass PageStorageExample extends StatelessWidget { final PageStorageBucket _bucket = PageStorageBucket(); Widget build(BuildContext context) { return PageStorage( bucket: _bucket, child: TabBarView( children: [ ListView.builder( key: PageStorageKey('list1'), // Preserves scroll position itemCount: 100, itemBuilder: (context, index) => ListTile( title: Text('Item $index'), ), ), ListView.builder( key: PageStorageKey('list2'), // Different key for different list itemCount: 100, itemBuilder: (context, index) => ListTile( title: Text('Item $index'), ), ), ], ), ); } } // Preserve expansion state class PreserveExpansionExample extends StatelessWidget { Widget build(BuildContext context) { return PageStorage( bucket: PageStorageBucket(), child: ListView.builder( itemCount: 20, itemBuilder: (context, index) => ExpansionTile( key: PageStorageKey('expansion_$index'), title: Text('Item $index'), children: [ ListTile(title: Text('Child 1')), ListTile(title: Text('Child 2')), ], ), ), ); } }
Key Selection Guide
dart// ❌ Wrong: Using index as key ListView.builder( itemCount: items.length, itemBuilder: (context, index) => Widget( key: ValueKey(index), // Breaks when list reorders child: Text(items[index]), ), ) // ✅ Correct: Using stable identifier ListView.builder( itemCount: items.length, itemBuilder: (context, index) => Widget( key: ValueKey(items[index].id), // Stable across reorders child: Text(items[index].name), ), ) // ❌ Wrong: Unnecessary key Container( key: ValueKey('container'), // Not needed for static widget child: Text('Static content'), ) // ✅ Correct: No key needed Container( child: Text('Static content'), )
Common Use Cases
1. Stateful Widget in List
dartclass StatefulList extends StatefulWidget { _StatefulListState createState() => _StatefulListState(); } class _StatefulListState extends State<StatefulList> { List<int> items = [1, 2, 3, 4, 5]; void shuffleItems() { setState(() => items.shuffle()); } Widget build(BuildContext context) { return Column( children: [ ...items.map((item) => StatefulItem( key: ValueKey(item), // Maintains individual state value: item, )), ElevatedButton( onPressed: shuffleItems, child: Text('Shuffle'), ), ], ); } } class StatefulItem extends StatefulWidget { final int value; const StatefulItem({Key? key, required this.value}) : super(key: key); _StatefulItemState createState() => _StatefulItemState(); } class _StatefulItemState extends State<StatefulItem> { bool isChecked = false; Widget build(BuildContext context) { return CheckboxListTile( title: Text('Item ${widget.value}'), value: isChecked, onChanged: (value) => setState(() => isChecked = value!), ); } }
2. Form Validation
dartclass MultiFormExample extends StatefulWidget { _MultiFormExampleState createState() => _MultiFormExampleState(); } class _MultiFormExampleState extends State<MultiFormExample> { final _personalFormKey = GlobalKey<FormState>(); final _addressFormKey = GlobalKey<FormState>(); void submitAll() { final personalValid = _personalFormKey.currentState!.validate(); final addressValid = _addressFormKey.currentState!.validate(); if (personalValid && addressValid) { print('All forms valid!'); } } Widget build(BuildContext context) { return Column( children: [ Form( key: _personalFormKey, child: Column( children: [ TextFormField( decoration: InputDecoration(labelText: 'Name'), validator: (value) => value?.isEmpty ?? true ? 'Required' : null, ), ], ), ), Form( key: _addressFormKey, child: Column( children: [ TextFormField( decoration: InputDecoration(labelText: 'Address'), validator: (value) => value?.isEmpty ?? true ? 'Required' : null, ), ], ), ), ElevatedButton( onPressed: submitAll, child: Text('Submit All'), ), ], ); } }
Performance Considerations
| Key Type | Performance | When to Use |
|---|---|---|
| ValueKey | Fast | Simple values, frequent updates |
| ObjectKey | Medium | Complex objects |
| UniqueKey | Slow | Force complete rebuild |
| GlobalKey | Slow | Access state/context |
| PageStorageKey | Fast | Preserve scroll position |
Best Practices
- Only use keys when necessary
- Use ValueKey for lists with unique identifiers
- Avoid using index as key in dynamic lists
- Use GlobalKey sparingly (performance cost)
- Prefer local keys over global keys
- Use PageStorageKey for navigation preservation
Important: Keys are essential for maintaining state in dynamic lists and optimizing widget tree updates. Choose the right key type based on your use case and performance requirements.
Learn more at Flutter Keys Documentation