Question #220MediumFlutter Basics

Different types of keys in flutter and explain them ?

#flutter

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 TypeUse CaseExample
ValueKeyUnique primitive valuesValueKey(1)
ObjectKeyComplex objectsObjectKey(user)
UniqueKeyAlways uniqueUniqueKey()
GlobalKeyAccess widget state/contextGlobalKey()
PageStorageKeyPreserve scroll positionPageStorageKey('list')

1. ValueKey

Used when widgets can be uniquely identified by a single value (int, string, etc.).

dart
class 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.

dart
class 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.

dart
class 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.

dart
class 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.

dart
class 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

dart
class 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

dart
class 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 TypePerformanceWhen to Use
ValueKeyFastSimple values, frequent updates
ObjectKeyMediumComplex objects
UniqueKeySlowForce complete rebuild
GlobalKeySlowAccess state/context
PageStorageKeyFastPreserve 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