what is the different key’s in flutter and what is the use of them in different scenario provide me with some example ?
#flutter
Answer
Different Keys in Flutter
Keys in Flutter help the framework identify which widgets have changed, been added, or removed. They're crucial for maintaining state and optimizing performance in dynamic UIs.
Types of Keys
| Key Type | Purpose | Example Scenario |
|---|---|---|
| ValueKey | Unique by value | List items with unique IDs |
| ObjectKey | Unique by object | Complex data objects |
| UniqueKey | Always unique | Force widget recreation |
| GlobalKey | Access state/context | Forms, scroll controllers |
| PageStorageKey | Preserve scroll position | Tabs, navigation |
1. ValueKey - For Simple Values
Use when items can be uniquely identified by a primitive value.
dart// Example: Todo list with reordering class TodoListExample extends StatefulWidget { _TodoListExampleState createState() => _TodoListExampleState(); } class _TodoListExampleState extends State<TodoListExample> { List<Todo> todos = [ Todo(id: 1, title: 'Buy groceries', isDone: false), Todo(id: 2, title: 'Walk dog', isDone: true), Todo(id: 3, title: 'Write code', isDone: false), ]; Widget build(BuildContext context) { return ReorderableListView( onReorder: (oldIndex, newIndex) { setState(() { if (newIndex > oldIndex) newIndex--; final todo = todos.removeAt(oldIndex); todos.insert(newIndex, todo); }); }, children: todos.map((todo) => TodoItem( key: ValueKey(todo.id), // Preserves state during reorder todo: todo, onToggle: () => toggleTodo(todo.id), )).toList(), ); } void toggleTodo(int id) { setState(() { final index = todos.indexWhere((t) => t.id == id); todos[index] = todos[index].copyWith( isDone: !todos[index].isDone, ); }); } } class Todo { final int id; final String title; final bool isDone; Todo({required this.id, required this.title, required this.isDone}); Todo copyWith({String? title, bool? isDone}) { return Todo( id: id, title: title ?? this.title, isDone: isDone ?? this.isDone, ); } } class TodoItem extends StatefulWidget { final Todo todo; final VoidCallback onToggle; const TodoItem({Key? key, required this.todo, required this.onToggle}) : super(key: key); _TodoItemState createState() => _TodoItemState(); } class _TodoItemState extends State<TodoItem> { bool _isExpanded = false; Widget build(BuildContext context) { return Card( child: ExpansionTile( title: Text( widget.todo.title, style: TextStyle( decoration: widget.todo.isDone ? TextDecoration.lineThrough : null, ), ), trailing: Checkbox( value: widget.todo.isDone, onChanged: (_) => widget.onToggle(), ), initiallyExpanded: _isExpanded, onExpansionChanged: (expanded) => setState(() => _isExpanded = expanded), children: [ Padding( padding: EdgeInsets.all(16), child: Text('Details for ${widget.todo.title}'), ), ], ), ); } }
2. ObjectKey - For Complex Objects
Use when the entire object defines uniqueness.
dart// Example: User cards with complex data class User { final int id; final String name; final String email; final DateTime lastActive; User({ required this.id, required this.name, required this.email, required this.lastActive, }); bool operator ==(Object other) => identical(this, other) || other is User && runtimeType == other.runtimeType && id == other.id && name == other.name && email == other.email; int get hashCode => id.hashCode ^ name.hashCode ^ email.hashCode; } class UserListExample extends StatefulWidget { _UserListExampleState createState() => _UserListExampleState(); } class _UserListExampleState extends State<UserListExample> { List<User> users = [ User(id: 1, name: 'Alice', email: 'alice@example.com', lastActive: DateTime.now()), User(id: 2, name: 'Bob', email: 'bob@example.com', lastActive: DateTime.now()), ]; void shuffleUsers() { setState(() => users.shuffle()); } Widget build(BuildContext context) { return Column( children: [ ...users.map((user) => UserCard( key: ObjectKey(user), // Uses entire object for identity user: user, )), ElevatedButton( onPressed: shuffleUsers, child: Text('Shuffle Users'), ), ], ); } } class UserCard extends StatefulWidget { final User user; const UserCard({Key? key, required this.user}) : super(key: key); _UserCardState createState() => _UserCardState(); } class _UserCardState extends State<UserCard> { bool _showDetails = false; Color _cardColor = Colors.white; Widget build(BuildContext context) { return Card( color: _cardColor, child: ListTile( title: Text(widget.user.name), subtitle: _showDetails ? Text(widget.user.email) : null, trailing: IconButton( icon: Icon(_showDetails ? Icons.expand_less : Icons.expand_more), onPressed: () => setState(() { _showDetails = !_showDetails; _cardColor = _showDetails ? Colors.blue[50]! : Colors.white; }), ), ), ); } }
3. UniqueKey - For Forced Rebuilds
Use to force complete widget recreation.
dart// Example: Resettable form class ResettableForm extends StatefulWidget { _ResettableFormState createState() => _ResettableFormState(); } class _ResettableFormState extends State<ResettableForm> { Key _formKey = UniqueKey(); // Changes on reset void resetForm() { setState(() { _formKey = UniqueKey(); // Forces new FormWidget instance }); } Widget build(BuildContext context) { return Column( children: [ FormWidget(key: _formKey), ElevatedButton( onPressed: resetForm, child: Text('Reset Form'), ), ], ); } } class FormWidget extends StatefulWidget { const FormWidget({Key? key}) : super(key: key); _FormWidgetState createState() => _FormWidgetState(); } class _FormWidgetState extends State<FormWidget> { final _nameController = TextEditingController(); final _emailController = TextEditingController(); void dispose() { _nameController.dispose(); _emailController.dispose(); super.dispose(); } Widget build(BuildContext context) { return Column( children: [ TextField( controller: _nameController, decoration: InputDecoration(labelText: 'Name'), ), TextField( controller: _emailController, decoration: InputDecoration(labelText: 'Email'), ), ], ); } } // Example: Random color generator class RandomColorBox extends StatefulWidget { _RandomColorBoxState createState() => _RandomColorBoxState(); } class _RandomColorBoxState extends State<RandomColorBox> { Key _boxKey = UniqueKey(); void regenerate() { setState(() { _boxKey = UniqueKey(); // Forces new ColorBox }); } Widget build(BuildContext context) { return Column( children: [ ColorBox(key: _boxKey), ElevatedButton( onPressed: regenerate, child: Text('New Color'), ), ], ); } } class ColorBox extends StatefulWidget { const ColorBox({Key? key}) : super(key: key); _ColorBoxState createState() => _ColorBoxState(); } class _ColorBoxState extends State<ColorBox> { late Color color; void initState() { super.initState(); color = Colors.primaries[Random().nextInt(Colors.primaries.length)]; } Widget build(BuildContext context) { return Container( width: 200, height: 200, color: color, ); } }
4. GlobalKey - For State Access
Use to access widget state or context from anywhere.
dart// Example: Form validation from parent class AdvancedFormExample extends StatefulWidget { _AdvancedFormExampleState createState() => _AdvancedFormExampleState(); } class _AdvancedFormExampleState extends State<AdvancedFormExample> { final GlobalKey<FormState> _formKey = GlobalKey<FormState>(); final GlobalKey<_PersonalInfoState> _personalInfoKey = GlobalKey<_PersonalInfoState>(); final GlobalKey<_AddressInfoState> _addressInfoKey = GlobalKey<_AddressInfoState>(); void submitAllForms() { // Validate main form if (!_formKey.currentState!.validate()) return; // Access child widget states directly final personalData = _personalInfoKey.currentState!.getData(); final addressData = _addressInfoKey.currentState!.getData(); print('Personal: $personalData'); print('Address: $addressData'); // Show success ScaffoldMessenger.of(context).showSnackBar( SnackBar(content: Text('Forms submitted successfully!')), ); } Widget build(BuildContext context) { return Scaffold( body: Form( key: _formKey, child: ListView( padding: EdgeInsets.all(16), children: [ PersonalInfo(key: _personalInfoKey), SizedBox(height: 20), AddressInfo(key: _addressInfoKey), SizedBox(height: 20), ElevatedButton( onPressed: submitAllForms, child: Text('Submit All'), ), ], ), ), ); } } class PersonalInfo extends StatefulWidget { const PersonalInfo({Key? key}) : super(key: key); _PersonalInfoState createState() => _PersonalInfoState(); } class _PersonalInfoState extends State<PersonalInfo> { final _nameController = TextEditingController(); final _ageController = TextEditingController(); Map<String, dynamic> getData() { return { 'name': _nameController.text, 'age': int.tryParse(_ageController.text) ?? 0, }; } Widget build(BuildContext context) { return Column( children: [ Text('Personal Information', style: TextStyle(fontSize: 20)), TextFormField( controller: _nameController, decoration: InputDecoration(labelText: 'Name'), validator: (value) => value?.isEmpty ?? true ? 'Required' : null, ), TextFormField( controller: _ageController, decoration: InputDecoration(labelText: 'Age'), keyboardType: TextInputType.number, validator: (value) { final age = int.tryParse(value ?? ''); if (age == null) return 'Invalid age'; if (age < 18) return 'Must be 18+'; return null; }, ), ], ); } } class AddressInfo extends StatefulWidget { const AddressInfo({Key? key}) : super(key: key); _AddressInfoState createState() => _AddressInfoState(); } class _AddressInfoState extends State<AddressInfo> { final _streetController = TextEditingController(); final _cityController = TextEditingController(); Map<String, String> getData() { return { 'street': _streetController.text, 'city': _cityController.text, }; } Widget build(BuildContext context) { return Column( children: [ Text('Address', style: TextStyle(fontSize: 20)), TextFormField( controller: _streetController, decoration: InputDecoration(labelText: 'Street'), validator: (value) => value?.isEmpty ?? true ? 'Required' : null, ), TextFormField( controller: _cityController, decoration: InputDecoration(labelText: 'City'), validator: (value) => value?.isEmpty ?? true ? 'Required' : null, ), ], ); } } // Example: Scroll to widget class ScrollToExample extends StatefulWidget { _ScrollToExampleState createState() => _ScrollToExampleState(); } class _ScrollToExampleState extends State<ScrollToExample> { final ScrollController _scrollController = ScrollController(); final GlobalKey _targetKey = GlobalKey(); void scrollToTarget() { final RenderBox renderBox = _targetKey.currentContext!.findRenderObject() as RenderBox; final position = renderBox.localToGlobal(Offset.zero); _scrollController.animateTo( position.dy + _scrollController.offset, duration: Duration(milliseconds: 500), curve: Curves.easeInOut, ); } Widget build(BuildContext context) { return Column( children: [ ElevatedButton( onPressed: scrollToTarget, child: Text('Scroll to Target'), ), Expanded( child: ListView( controller: _scrollController, children: [ ...List.generate(20, (i) => ListTile(title: Text('Item $i'))), Container( key: _targetKey, height: 100, color: Colors.red, child: Center(child: Text('TARGET', style: TextStyle(color: Colors.white))), ), ...List.generate(20, (i) => ListTile(title: Text('Item ${i + 20}'))), ], ), ), ], ); } }
5. PageStorageKey - Preserve Scroll Position
Use to maintain scroll position across navigation.
dart// Example: Tabbed view with preserved scroll class TabbedScrollExample extends StatelessWidget { final PageStorageBucket _bucket = PageStorageBucket(); Widget build(BuildContext context) { return DefaultTabController( length: 3, child: Scaffold( appBar: AppBar( title: Text('Preserved Scroll'), bottom: TabBar( tabs: [ Tab(text: 'Tab 1'), Tab(text: 'Tab 2'), Tab(text: 'Tab 3'), ], ), ), body: PageStorage( bucket: _bucket, child: TabBarView( children: [ ScrollableList(key: PageStorageKey('tab1')), ScrollableList(key: PageStorageKey('tab2')), ScrollableList(key: PageStorageKey('tab3')), ], ), ), ), ); } } class ScrollableList extends StatelessWidget { const ScrollableList({Key? key}) : super(key: key); Widget build(BuildContext context) { return ListView.builder( itemCount: 100, itemBuilder: (context, index) => ListTile( title: Text('Item $index'), subtitle: Text('Scroll position preserved!'), ), ); } }
Best Practices
dart// ✅ Good: Use ValueKey for stable identifiers ListView( children: items.map((item) => Widget( key: ValueKey(item.id), child: Text(item.name), )).toList(), ) // ❌ Bad: Using index as key ListView.builder( itemCount: items.length, itemBuilder: (context, index) => Widget( key: ValueKey(index), // Breaks on reorder! child: Text(items[index]), ), ) // ✅ Good: GlobalKey for accessing state final formKey = GlobalKey<FormState>(); formKey.currentState!.validate(); // ❌ Bad: Overusing GlobalKey // Creates new GlobalKey on every build Widget build(BuildContext context) { final key = GlobalKey(); // Memory leak! return Container(key: key); }
Remember: Only use keys when necessary for maintaining state in dynamic lists or accessing widget state. Choose the simplest key type that meets your requirements.
Learn more at Flutter Keys Deep Dive