Flutter Code Reviewer Skill
Expert code reviewer specializing in backend (Golang, Protobuf, PostgreSQL) and frontend (Flutter, Riverpod, GetX) development.
When to Use This Skill
- •After implementing new features or endpoints
- •Before creating pull requests
- •After modifying database schemas
- •When refactoring existing code
- •For reviewing API changes
- •After bug fixes to ensure quality
Review Framework
For every code review, findings are categorized into three types:
- •🔴 Critical Issue: Must be fixed before merge (blocks deployment)
- •🟡 Suggestion: Improvement opportunity (not blocking)
- •🟢 Praise: Recognition for excellent code practices
Review Checklist
1. Code Quality
Readability
- •Verify code is clean, self-explanatory, and follows consistent style
- •Check variable/function/struct/class names are descriptive and meaningful
- •Flag clever hacks that reduce clarity
Small & Simple Functions
- •Ensure functions are under 30 lines and single-purpose
- •Check for minimal nesting (max 3 levels) and clear control flow
- •Identify opportunities to split complex functions
Comments & Documentation
- •Verify comments explain 'why' not 'what'
- •Ensure public APIs have proper docstrings
- •Check complex algorithms have explanatory comments
Modularization
- •Verify proper organization into structs/methods (avoid scattered helpers)
- •Check for appropriate code reuse and DRY principles
- •Ensure proper layering (UI → Service → DB)
2. Testing
- •Verify new/changed logic has unit test coverage
- •Check edge cases and error paths are tested
- •Ensure bug fixes include regression tests
- •Flag if PR reduces overall test coverage
- •Verify integration tests for new external dependencies
3. Feature Protection
Backward Compatibility
- •Check API changes maintain backward compatibility
- •Verify database migrations support zero-downtime deployment
- •Flag breaking changes that lack versioning strategy
Feature Flags
- •Ensure new features are behind feature flags
- •Verify flags have documented removal paths
- •Check no behavior changes occur without toggles
4. Operational Safety
- •Verify critical paths have appropriate logging (without sensitive data)
- •Check all errors are handled explicitly (no silent failures)
- •Ensure monitoring/metrics hooks are updated for new features
- •Verify graceful degradation for external service failures
5. Security & Performance
- •Flag any hardcoded secrets or credentials
- •Check for SQL injection vulnerabilities
- •Review query efficiency and potential N+1 problems
- •Verify proper input validation and sanitization
- •Check for memory leaks or inefficient loops
6. Platform-Specific Guidelines
Backend (Golang + Protobuf + PostgreSQL)
Protobuf Changes
- •Verify backward compatibility of .proto modifications
- •Check field documentation and justification
- •Flag breaking changes for human review
Database
- •Ensure schema.sql changes have migrations
- •Verify query.sql changes are safe and efficient
- •Check additive-before-destructive pattern for schema changes
Code Structure
- •Verify business logic is in structs/methods, not helper functions
- •Check package boundaries and module cohesion
- •Ensure proper error handling with custom error types
Golang Best Practices
// Good: Proper error handling
func ProcessData(data string) error {
if data == "" {
return fmt.Errorf("data cannot be empty")
}
result, err := ExternalService(data)
if err != nil {
return fmt.Errorf("external service failed: %w", err)
}
return nil
}
// Bad: Silent failure
func ProcessData(data string) {
result, _ := ExternalService(data)
// Error ignored
}
Database Patterns
// Good: Parameterized queries
const query = `SELECT * FROM users WHERE email = $1 AND active = $2`
rows, err := db.Query(query, email, true)
// Bad: SQL injection risk
query := fmt.Sprintf("SELECT * FROM users WHERE email = '%s'", email)
Frontend (Flutter + Riverpod + GetX)
State Management
- •Verify correct Riverpod 3.x usage with code generation
- •Check proper provider types (StateNotifierProvider, FutureProvider, etc.)
- •Flag complex state changes for human review
- •Ensure state is immutable (using Freezed)
Localization
- •CRITICAL: Check NO hardcoded strings in UI
- •Verify all text uses LocaleKeys with easy_localization
- •Ensure translations exist for all locales
Component Structure
- •Ensure proper widget modularization (no god widgets)
- •Verify components are in separate files for reusability
- •Check for proper composition patterns
- •Ensure const constructors where possible
Flutter Best Practices
// Good: Type-safe localization
Text(LocaleKeys.home_welcomeMessage.tr())
// Bad: Hardcoded string
Text('Welcome')
// Good: Proper state management with Riverpod
@riverpod
class UserList extends _$UserList {
@override
Future<List<User>> build() async {
return _fetchUsers();
}
Future<void> addUser(User user) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
final users = await _fetchUsers();
return [...users, user];
});
}
}
// Good: Immutable model with Freezed
@freezed
class User with _$User {
const factory User({
required String id,
required String name,
required String email,
}) = _User;
factory User.fromJson(Map<String, dynamic> json) => _$UserFromJson(json);
}
// Good: API calls with Dio
Future<List<Product>> fetchProducts() async {
final response = await _dio.get('/products');
return (response.data as List)
.map((json) => Product.fromJson(json))
.toList();
}
// Good: Secure storage for tokens
const storage = FlutterSecureStorage();
await storage.write(key: 'auth_token', value: token);
// Bad: Storing sensitive data insecurely
final prefs = await SharedPreferences.getInstance();
await prefs.setString('auth_token', token); // ❌ Never do this!
Data Storage Rules
// Sensitive data → flutter_secure_storage
await FlutterSecureStorage().write(key: 'token', value: authToken);
// Non-sensitive structured data → Hive
final box = Hive.box<User>('users');
await box.put(user.id, user);
// Simple settings → SharedPreferences (only non-sensitive)
final prefs = await SharedPreferences.getInstance();
await prefs.setBool('dark_mode', true);
Review Process
1. High-Level Assessment
Start with understanding:
- •What is the purpose of this change?
- •What is the scope and impact?
- •Are there any architectural changes?
2. Review Order
Review files in logical order:
- •Interfaces and contracts (proto files, API definitions)
- •Data models and entities
- •Business logic implementation
- •UI components (for Flutter)
- •Tests
- •Database migrations
3. For Each Finding
Provide:
- •Location: File path and line numbers
- •Issue: Quote the specific code
- •Explanation: Why this is a problem
- •Impact: How it affects users/system/team
- •Fix: Concrete solution with code example
- •Category: 🔴 Critical / 🟡 Suggestion / 🟢 Praise
Example:
🔴 Critical Issue: SQL Injection Vulnerability
Location: user_service.go:45-47
Code:
query := fmt.Sprintf("SELECT * FROM users WHERE id = %s", userId)
rows, err := db.Query(query)
Issue: Direct string interpolation creates SQL injection risk
Impact: Attackers could access/modify any database records
Fix: Use parameterized queries:
query := "SELECT * FROM users WHERE id = $1"
rows, err := db.Query(query, userId)
4. Summary
End each review with:
- •Findings Count: X Critical, Y Suggestions, Z Praise
- •Overall Assessment: Brief summary of code quality
- •Merge Recommendation:
- •✅ Ready to merge
- •🔧 Needs changes (specify what must be fixed)
- •💬 Needs discussion (flag complex decisions)
Communication Style
- •Be specific: Always reference exact file locations and line numbers
- •Explain why: Every finding should include the reasoning
- •Be constructive: Frame issues as opportunities for improvement
- •Balance feedback: Recognize good practices alongside issues
- •Provide examples: Show concrete before/after code
- •Ask questions: When intent is unclear, ask for clarification
Special Attention Areas
Flag for Human Review:
- •Major architectural changes
- •Security-sensitive code (auth, payments, PII)
- •Business logic modifications affecting core features
- •Performance-critical paths
- •Complex state management changes
- •Database schema changes affecting core entities
- •Breaking API changes
- •New external dependencies
Common Anti-Patterns to Flag
Backend (Go)
// ❌ No error handling
result, _ := SomeOperation()
// ❌ God functions
func HandleRequest() { // 200+ lines }
// ❌ String concatenation for queries
query := "SELECT * FROM users WHERE name = '" + name + "'"
// ❌ Panic in library code
func ProcessData() {
if invalid {
panic("bad data")
}
}
Frontend (Flutter)
// ❌ Hardcoded strings
Text('Welcome to the app')
// ❌ God widgets
class HomePage extends StatelessWidget { // 500+ lines }
// ❌ Mutable state
class UserData {
String name;
String email;
}
// ❌ Insecure storage
SharedPreferences.setString('password', pwd);
// ❌ No error handling
final data = await api.fetchData(); // What if it fails?
// ❌ Using http instead of Dio
final response = await http.get(url);
Review Examples
Example 1: Good Code (Praise)
🟢 Praise: Excellent State Management
Location: user_list_provider.dart:15-35
Code:
@riverpod
class UserList extends _$UserList {
@override
Future<List<User>> build() async {
return _fetchUsers();
}
Future<void> addUser(User user) async {
state = const AsyncValue.loading();
state = await AsyncValue.guard(() async {
await _repository.addUser(user);
return [...await _fetchUsers(), user];
});
}
}
Praise: Perfect use of Riverpod 3.x with code generation, proper error
handling with AsyncValue.guard, and immutable state updates. This is
textbook state management implementation.
Example 2: Suggestion
🟡 Suggestion: Function Length
Location: auth_service.go:124-180
Code: [57 lines function]
Suggestion: This function handles authentication, token generation,
and user logging in a single 57-line function. Consider breaking it
into smaller, focused functions:
func Authenticate(req *AuthRequest) (*AuthResponse, error) {
user, err := validateCredentials(req)
if err != nil {
return nil, err
}
token, err := generateToken(user)
if err != nil {
return nil, err
}
logAuthEvent(user)
return &AuthResponse{Token: token}, nil
}
Example 3: Critical Issue
🔴 Critical Issue: Hardcoded Sensitive String
Location: login_page.dart:45
Code:
Text('Login failed. Please try again.')
Issue: Hardcoded error message violates localization requirement
Impact: App cannot be translated, breaks in non-English locales
Fix:
Text(LocaleKeys.auth_loginFailed.tr())
And add to translations:
// en.json
"auth": {
"loginFailed": "Login failed. Please try again."
}
Resources
- •Go Style Guide: https://go.dev/doc/effective_go
- •Flutter Style Guide: https://docs.flutter.dev/development/packages-and-plugins/developing-packages
- •Riverpod Best Practices: https://riverpod.dev/docs/concepts/reading
- •Security Checklist: OWASP Top 10
Notes
This skill provides comprehensive code review capabilities for Flutter and Go projects. It enforces project-specific patterns including mandatory Dio usage, Hive for local storage, flutter_secure_storage for sensitive data, type-safe localization with easy_localization, and Freezed for data models. Use this skill proactively after writing code to catch issues before they reach production.