Using GlobalKey and UniqueKey in Flutter

In Flutter, keys are fundamental for identifying, controlling, and maintaining the state of widgets, especially when the widget tree changes dynamically. Two essential types of keys are GlobalKey and UniqueKey. While both serve as identifiers for widgets, they have distinct use cases and implications for widget management and state persistence.

Understanding Keys in Flutter

Keys in Flutter are objects that can be attached to widgets, acting as stable identifiers across rebuilds. They help Flutter efficiently update the widget tree, preserving state where appropriate. Understanding the role and types of keys is crucial for managing complex widget interactions and state.

What is a UniqueKey?

UniqueKey is a type of key that generates a new unique identifier each time it is created. As the name suggests, no two UniqueKey instances will ever be the same. They are generally used to force a widget to rebuild when it’s logically replaced, especially in scenarios involving animations or state changes that aren’t easily diffed.

When to Use UniqueKey

  • Forcing Widget Rebuilds: When you need a widget to completely rebuild, regardless of whether its configuration changes.
  • Dynamic Lists: Useful in dynamic lists where adding or removing elements should trigger a proper re-initialization.

What is a GlobalKey?

GlobalKey is a type of key that is globally unique throughout the entire app. Unlike UniqueKey, which is unique only within its parent, GlobalKey provides a way to access the underlying State object of a StatefulWidget from anywhere in the application. This is particularly useful for controlling widgets from outside their direct parent, such as accessing the state of a form or triggering actions on a widget in a different part of the screen.

When to Use GlobalKey

  • Accessing Widget State: When you need to directly interact with the state of a StatefulWidget from a different part of the app.
  • Form Validation: A common use case is accessing and validating the Form widget’s state.
  • Navigation: Controlling navigation, showing dialogs, or manipulating overlays.

Implementing UniqueKey

Using UniqueKey is straightforward. Attach it to a widget to ensure that a new widget instance is created each time.

Example: Forcing a Widget Rebuild with UniqueKey


import 'package:flutter/material.dart';

class UniqueKeyExample extends StatefulWidget {
  @override
  _UniqueKeyExampleState createState() => _UniqueKeyExampleState();
}

class _UniqueKeyExampleState extends State {
  int counter = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('UniqueKey Example'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text(
              'Counter: $counter',
            ),
            SizedBox(height: 20),
            // Using UniqueKey to force a rebuild of the NumberDisplay widget
            NumberDisplay(
              key: UniqueKey(), // Each build creates a new key
              number: counter,
            ),
          ],
        ),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          setState(() {
            counter++;
          });
        },
        child: Icon(Icons.add),
      ),
    );
  }
}

class NumberDisplay extends StatefulWidget {
  final int number;

  NumberDisplay({Key? key, required this.number}) : super(key: key);

  @override
  _NumberDisplayState createState() => _NumberDisplayState();
}

class _NumberDisplayState extends State {
  @override
  void initState() {
    super.initState();
    print('NumberDisplay Widget Created with Number: ${widget.number}');
  }

  @override
  Widget build(BuildContext context) {
    return Text(
      'Number: ${widget.number}',
      style: TextStyle(fontSize: 20),
    );
  }

  @override
  void didUpdateWidget(NumberDisplay oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (oldWidget.number != widget.number) {
      print('NumberDisplay Widget Updated with Number: ${widget.number}');
    }
  }

  @override
  void dispose() {
    print('NumberDisplay Widget Disposed with Number: ${widget.number}');
    super.dispose();
  }
}

In this example, the NumberDisplay widget is forced to rebuild every time the counter increments because it’s given a new UniqueKey each time it’s rendered. This triggers the initState and disposes of the old widget, proving that the widget is entirely rebuilt.

Implementing GlobalKey

Using GlobalKey allows you to access the state of a StatefulWidget from anywhere in your app. Here’s an example demonstrating its use in validating a form.

Example: Validating a Form with GlobalKey


import 'package:flutter/material.dart';

class GlobalKeyExample extends StatefulWidget {
  @override
  _GlobalKeyExampleState createState() => _GlobalKeyExampleState();
}

class _GlobalKeyExampleState extends State {
  // Create a GlobalKey for the form
  final GlobalKey _formKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('GlobalKey Example'),
      ),
      body: Padding(
        padding: EdgeInsets.all(16.0),
        child: Form(
          key: _formKey, // Attach the GlobalKey to the Form widget
          child: Column(
            children: [
              TextFormField(
                decoration: InputDecoration(labelText: 'Name'),
                validator: (value) {
                  if (value == null || value.isEmpty) {
                    return 'Please enter your name';
                  }
                  return null;
                },
              ),
              SizedBox(height: 20),
              ElevatedButton(
                onPressed: () {
                  // Access and validate the form using the GlobalKey
                  if (_formKey.currentState!.validate()) {
                    ScaffoldMessenger.of(context).showSnackBar(
                      SnackBar(content: Text('Form is valid!')),
                    );
                  }
                },
                child: Text('Validate Form'),
              ),
            ],
          ),
        ),
      ),
    );
  }
}

In this example:

  • A GlobalKey is created (_formKey).
  • This key is assigned to the Form widget.
  • The button’s onPressed callback uses the GlobalKey to access the form’s state (_formKey.currentState) and calls the validate() method to check if the form is valid.

Key Differences Between UniqueKey and GlobalKey

  • Uniqueness: UniqueKey is unique within its parent only, whereas GlobalKey is globally unique.
  • Access to State: GlobalKey provides access to a widget’s State object; UniqueKey does not.
  • Use Cases: UniqueKey is used for forcing rebuilds; GlobalKey is used for accessing and manipulating widget state from anywhere in the app.

Best Practices

  • Avoid Overuse of GlobalKey: GlobalKey can lead to tight coupling between different parts of your app, making it harder to maintain. Consider using callback functions or state management solutions like Provider or BLoC to manage state more efficiently.
  • Understand the Impact of UniqueKey: Be mindful when using UniqueKey as it forces a full rebuild, which can be costly in terms of performance.
  • Proper State Management: Always aim for predictable state management. Use keys as necessary but lean towards more structured approaches when possible.

Conclusion

Understanding and utilizing GlobalKey and UniqueKey effectively in Flutter can significantly enhance your ability to manage widget state and force rebuilds when needed. By carefully choosing the right type of key for your use case and following best practices, you can build more robust and maintainable Flutter applications.