Flutter Key Lints
A Flutter custom lint package to enforce proper widget key usage for better Flutter application performance.
Why Keys Matter
Keys in Flutter are essential for:
- Widget Reconciliation: Help Flutter distinguish between widget instances during rebuilds
- State Preservation: Ensure widget state is maintained when widgets change position
- Performance Optimization: Reduce unnecessary widget rebuilds
- Animation: Enable smooth transitions between widget states
Installation
Add to your pubspec.yaml:
dev_dependencies:
custom_lint: ^0.7.5
flutter_key_lints: ^0.1.0
Configure in your analysis_options.yaml:
analyzer:
plugins:
- custom_lint
custom_lint:
rules:
- require_widget_key: true
- list_item_key: true
- appropriate_key_type: true
- animation_key: true
- performance_impact: true
Usage
Run custom lint to identify widgets missing keys:
dart run custom_lint
Rule Details
The package provides several rules to enforce best practices for widget keys:
1. require_widget_key
Flags widgets that should have keys but don't:
❌ Incorrect (missing key):
ListView(
children: [
ListTile(
title: Text('Item 1'),
),
],
);
✅ Correct (with key):
ListView(
children: [
ListTile(
key: ValueKey('item-1'),
title: Text('Item 1'),
),
],
);
2. list_item_key
Specifically targets list items in ListView.builder, GridView.builder, etc., to ensure they have keys:
❌ Incorrect (missing key in list item):
ListView.builder(
itemBuilder: (context, index) => ListTile(
title: Text('Item $index'),
),
);
✅ Correct (with key):
ListView.builder(
itemBuilder: (context, index) => ListTile(
key: ValueKey(index),
title: Text('Item $index'),
),
);
3. appropriate_key_type
Enforces the use of appropriate key types in different scenarios:
❌ Incorrect (overuse of GlobalKey):
// GlobalKey shouldn't be used unless needed for state access
Container(key: GlobalKey());
❌ Incorrect (raw Key usage):
// Raw Key constructor should be avoided
Container(key: Key('my-key'));
✅ Correct (appropriate key types):
// ValueKey for value-based identification
Container(key: ValueKey('container-1'));
// UniqueKey for truly unique instances
Container(key: UniqueKey());
// GlobalKey when state access is needed
final formKey = GlobalKey<FormState>();
Form(key: formKey, child: Container());
4. animation_key
Enforces the use of keys in animation widgets to prevent flickering and state loss:
❌ Incorrect (missing key for animation):
AnimatedContainer(
duration: Duration(milliseconds: 300),
color: isActive ? Colors.blue : Colors.grey,
width: isActive ? 100 : 50,
); // Missing key
✅ Correct (with key for animation):
AnimatedContainer(
key: ValueKey('my-animated-container'),
duration: Duration(milliseconds: 300),
color: isActive ? Colors.blue : Colors.grey,
width: isActive ? 100 : 50,
);
5. performance_impact
Analyzes the potential performance impact of missing keys and provides severity levels:
❌ Critical Impact (high performance penalty):
ListView.builder(
itemBuilder: (context, index) => ListTile(
// No key results in complete list rebuilds
title: Text('Item $index'),
),
);
❌ High Impact (significant performance penalty):
// Conditional widget without key causes state loss
if (isLoggedIn) {
return UserDashboard();
} else {
return LoginScreen();
}
✅ With Performance Analysis (showing the fix):
// Conditional widget with key preserves state
if (isLoggedIn) {
return UserDashboard(key: ValueKey('dashboard'));
} else {
return LoginScreen(key: ValueKey('login'));
}
Customizing Rules
You can customize rule behavior in your analysis_options.yaml:
custom_lint:
rules:
# Enable/disable specific rules
- require_widget_key: true
- list_item_key: true
- appropriate_key_type: true
- animation_key: true
- performance_impact: true
# Custom configuration for require_widget_key
# Override the default exempt widget list
require_widget_key:
exempt_widgets:
- "SizedBox"
- "Divider"
- "Padding"
- "Container" # Add your own exemptions
Exempt Widgets
Some widgets don't require keys by default:
- Layout widgets (SizedBox, Divider, Padding, etc.)
- Decoration widgets (Opacity, Transform, etc.)
- Inherited widgets
Best Practices
- Always use keys for items in lists, grids, or any reorderable content
- Use keys when conditionally rendering widgets
- Choose appropriate key types:
ValueKey: For value-based identificationUniqueKey: For truly unique instancesGlobalKey: When you need to access the widget's state
Known Issues & Workarounds
If you encounter compatibility issues between custom_lint and analyzer versions:
- Pin versions: Ensure compatible analyzer and custom_lint versions
- Manual review: Check list items and conditionally rendered widgets
- Use IDE linting: VS Code/Android Studio Flutter plugins also help identify key issues
License
MIT (c) Jordan M. Adler 2025
Additional information
This package is designed to help Flutter developers improve their code quality by enforcing proper widget key usage. Keys are often overlooked but can significantly impact performance and state management.
Contributing
Contributions are welcome! Here's how you can help:
- Report issues: If you find a bug or have a feature request, please open an issue
- Submit pull requests: Feel free to improve the package or add new lint rules
- Share feedback: Let us know how this package helps your development workflow
Running Tests
To run the tests for this package:
flutter test
Documentation
For more in-depth information about widget keys in Flutter:
- Widget Keys Guide - Comprehensive guide to understanding and using keys
- Widget Keys Cheatsheet - Quick reference for proper key usage
- Rules Reference - Detailed information about each lint rule
- Contributing Guide - How to contribute to the project
- Troubleshooting - Solutions to common issues
Additional documentation can be found in the doc directory.
Libraries
- flutter_key_lints
- Flutter Widget Key Linting Rules
- main