| name | dart-flutter-code-review |
| description | Library-agnostic Flutter/Dart code review checklist covering Widget best practices, state management patterns (BLoC, Riverpod, Provider, GetX, MobX, Signals), Dart idioms, performance, accessibility, security, and clean architecture. |
Flutter/Dart Code Review Best Practices
A comprehensive, library-agnostic checklist for reviewing Flutter/Dart applications. These principles apply regardless of which state management solution, routing library, or dependency injection framework is used.
1. General Project Health
2. Dart Language Pitfalls
3. Widget Best Practices
Widget Decomposition:
Const Usage:
Key Usage:
Theming and Design Systems:
Build Method Complexity:
4. State Management (Library-Agnostic)
These principles apply to all Flutter state management solutions (BLoC, Riverpod, Provider, GetX, MobX, Signals, ValueNotifier, etc.).
Architecture:
Immutability and Value Equality (for immutable state solutions: BLoC, Riverpod, Redux):
Reactive Discipline (for reactive mutation solutions: MobX, GetX, Signals):
State Shape Design:
// BAD — boolean flag soup allows impossible states
class UserState {
bool isLoading = false;
bool hasError = false; // isLoading && hasError is representable!
User? user;
}
// GOOD (immutable approach) — sealed types make impossible states unrepresentable
sealed class UserState {}
class UserInitial extends UserState {}
class UserLoading extends UserState {}
class UserLoaded extends UserState {
final User user;
const UserLoaded(this.user);
}
class UserError extends UserState {
final String message;
const UserError(this.message);
}
// GOOD (reactive approach) — observable enum + data, mutations via reactivity API
// enum UserStatus { initial, loading, loaded, error }
// Use your solution's observable/signal to wrap status and data separately
Rebuild Optimization:
Subscriptions and Cleanup:
Local State vs Global State:
5. Performance
Unnecessary Rebuilds:
Expensive Operations in build():
Image Optimization:
Lazy Loading:
Other:
6. Testing
Test Types and Expectations:
Coverage Targets:
Test Isolation:
Widget Test Quality:
7. Accessibility
Semantic Widgets:
Screen Reader Support:
Visual Accessibility:
Interaction Accessibility:
8. Platform-Specific Considerations
iOS/Android Differences:
Responsive Design:
9. Security
Secure Storage:
API Key Handling:
Input Validation:
Network Security:
10. Package/Dependency Review
Evaluating pub.dev Packages:
Version Constraints:
Monorepo-Specific (melos/workspace):
11. Navigation and Routing
General Principles (applies to any routing solution):
12. Error Handling
Framework Error Handling:
Error Reporting:
Graceful Degradation:
13. Internationalization (l10n)
Setup:
Content:
Code Review:
14. Dependency Injection
Principles (applies to any DI approach):
15. Static Analysis
Configuration:
Enforcement:
Key Rules to Verify Regardless of Lint Package Used:
State Management Quick Reference
This table maps general principles to implementations in popular solutions. Use this table to adapt review rules to whatever solution your project uses.
| Principle | BLoC/Cubit | Riverpod | Provider | GetX | MobX | Signals | Built-in |
|---|
| State Container | Bloc/Cubit | Notifier/AsyncNotifier | ChangeNotifier | GetxController | Store | signal() | StatefulWidget |
| UI Consumer | BlocBuilder | ConsumerWidget | Consumer | Obx/GetBuilder | Observer | Watch | setState |
| Selectors | BlocSelector/buildWhen | ref.watch(p.select(...)) | Selector | N/A | computed | computed() | N/A |
| Side Effects | BlocListener | ref.listen | Consumer callbacks | ever()/once() | reaction | effect() | Callbacks |
| Disposal | Automatic via BlocProvider | .autoDispose | Automatic via Provider | onClose() | ReactionDisposer | Manual | dispose() |
| Testing | blocTest() | ProviderContainer | Direct ChangeNotifier | Get.put in tests | Direct store test | Direct signal test | Widget test |
Sources