Question #225EasyFlutter Basics

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 TypePurposeExample Scenario
ValueKeyUnique by valueList items with unique IDs
ObjectKeyUnique by objectComplex data objects
UniqueKeyAlways uniqueForce widget recreation
GlobalKeyAccess state/contextForms, scroll controllers
PageStorageKeyPreserve scroll positionTabs, 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