Skip to main content

Code Quality Guidelines

Overview

This document outlines code quality standards and best practices for the AI-Powered Photo Journaling Flutter app.

Code Organization

Directory Structure

lib/
├── app.dart                    # Main app widget
├── main.dart                   # App entry point
├── config/                     # Configuration
│   ├── constants.dart
│   ├── env.dart
│   ├── routes/
│   └── theme/
├── core/                       # Core utilities
│   ├── accessibility/
│   ├── animations/
│   ├── database/
│   ├── error/
│   ├── interactions/
│   ├── services/
│   ├── utils/
│   └── widgets/
├── features/                   # Feature modules
│   ├── entry/
│   ├── timeline/
│   ├── search/
│   ├── calendar/
│   ├── settings/
│   └── shared/
└── shared/                     # Shared widgets
    └── widgets/

Feature Module Structure

Each feature follows clean architecture:
feature_name/
├── data/
│   ├── models/               # Data models
│   └── services/             # API/database services
├── domain/
│   ├── entities/             # Business entities
│   └── repositories/         # Repository interfaces
└── presentation/
    ├── bloc/                 # State management
    ├── screens/              # Screen widgets
    └── widgets/              # Feature-specific widgets

Coding Standards

Dart Style Guide

Follow Effective Dart:
  1. Naming:
    • Classes: PascalCase
    • Functions/variables: camelCase
    • Constants: lowerCamelCase
    • Files: snake_case.dart
  2. Formatting:
    • Use dart format . before committing
    • Max line length: 80 characters
    • Use trailing commas for better diffs
  3. Imports:
    • Organize imports: dart → flutter → packages → relative
    • Use relative imports for project files
    • Avoid unused imports

Widget Best Practices

  1. Use const constructors wherever possible:
// Good
const Text('Hello')
const SizedBox(height: 16)

// Bad
Text('Hello')
SizedBox(height: 16)
  1. Split large widgets into smaller, focused widgets:
// Good
class UserProfile extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const _ProfileHeader(),
        const _ProfileStats(),
        const _ProfileActions(),
      ],
    );
  }
}

class _ProfileHeader extends StatelessWidget {
  const _ProfileHeader();

  @override
  Widget build(BuildContext context) {
    // ...
  }
}
  1. Use keys for list items:
ListView.builder(
  itemBuilder: (context, index) {
    return ListItem(
      key: ValueKey(items[index].id),
      item: items[index],
    );
  },
)
  1. Implement proper dispose:
class MyWidget extends StatefulWidget {
  @override
  State<MyWidget> createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> {
  late ScrollController _controller;

  @override
  void initState() {
    super.initState();
    _controller = ScrollController();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return ListView(controller: _controller);
  }
}

State Management

  1. BLoC Pattern:
    • Use flutter_bloc for complex state
    • Keep business logic in BLoC
    • Use events for actions, states for UI updates
  2. State Locality:
    • Keep state as local as possible
    • Avoid global state when not needed
    • Use StatefulWidget for local state
  3. Immutability:
    • Use immutable state classes
    • Use equatable for value equality
    • Avoid mutable collections in state

Error Handling

  1. Use try-catch for async operations:
try {
  final result = await apiCall();
  return result;
} catch (error, stackTrace) {
  ErrorHandler.logError(error, stackTrace: stackTrace);
  rethrow;
}
  1. Provide user-friendly messages:
ErrorHandler.showErrorDialog(
  context,
  error,
  userMessage: 'Failed to load entries. Please try again.',
);
  1. Log errors in debug mode:
if (kDebugMode) {
  debugPrint('Error: $error');
}

Performance Optimization

Widget Performance

  1. Const constructors: Prevent unnecessary rebuilds
  2. RepaintBoundary: Isolate expensive widgets
  3. ListView.builder: For long lists
  4. Lazy loading: Load data on demand
  5. Image caching: Use cached_network_image

Memory Management

  1. Dispose controllers: Always dispose in dispose()
  2. Cancel subscriptions: Cancel streams and listeners
  3. Weak references: Use when appropriate
  4. Image memory: Configure maxCacheSize and maxCacheSizeBytes

Build Optimization

  1. Use profile mode for performance testing
  2. Enable obfuscation in release builds
  3. Tree-shake icons: Remove unused icons
  4. Split debug info: Reduce app size

Testing

Unit Tests

Test business logic and utilities:
test('should calculate total correctly', () {
  final calculator = Calculator();
  expect(calculator.add(2, 3), equals(5));
});

Widget Tests

Test widget behavior:
testWidgets('should display error message', (tester) async {
  await tester.pumpWidget(
    ErrorDisplay(message: 'Test error'),
  );

  expect(find.text('Test error'), findsOneWidget);
});

Integration Tests

Test complete flows:
testWidgets('should create journal entry', (tester) async {
  // Navigate to create entry
  await tester.tap(find.byIcon(Icons.add));
  await tester.pumpAndSettle();

  // Fill form
  await tester.enterText(find.byKey(Key('title')), 'Test Entry');

  // Submit
  await tester.tap(find.text('Save'));
  await tester.pumpAndSettle();

  // Verify
  expect(find.text('Test Entry'), findsOneWidget);
});

Documentation

Code Comments

  1. Document public APIs:
/// Creates a journal entry with the given [title] and [content].
///
/// Throws [ValidationException] if title is empty.
/// Returns the created entry's ID.
Future<String> createEntry({
  required String title,
  required String content,
}) async {
  // Implementation
}
  1. Explain complex logic:
// Calculate weighted average based on emotion intensity
// Higher intensity emotions have more weight in the calculation
final weightedAverage = emotions
    .map((e) => e.value * e.intensity)
    .reduce((a, b) => a + b) / totalIntensity;
  1. Use TODO for future work:
// TODO(username): Implement offline support
// TODO: Add unit tests for edge cases

README Files

  • Project root: Setup and overview
  • Feature modules: Feature-specific docs
  • Complex algorithms: Explanation and examples

Code Review Checklist

Before Submitting PR

  • Code follows style guide
  • All tests passing
  • No linting warnings
  • Proper error handling
  • Performance considered
  • Accessibility tested
  • Documentation updated
  • No debug code (print statements, etc.)
  • No commented-out code
  • Commit messages follow convention

Reviewing Code

Look for:
  • Code readability
  • Proper abstractions
  • Error handling
  • Edge cases covered
  • Performance implications
  • Security considerations
  • Test coverage
  • Documentation quality

Common Anti-Patterns to Avoid

1. God Widgets

Bad:
class MassiveWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        // 500 lines of nested widgets
      ],
    );
  }
}
Good:
class OrganizedWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        const _Header(),
        const _Content(),
        const _Footer(),
      ],
    );
  }
}

2. setState in Parent

Bad:
setState(() {
  // Rebuilds entire parent and all children
});
Good:
// Use BLoC or local state in child widgets

3. Unnecessary Rebuilds

Bad:
Widget build(BuildContext context) {
  return Container(
    child: Text('Static text'), // Rebuilds on every parent rebuild
  );
}
Good:
Widget build(BuildContext context) {
  return Container(
    child: const Text('Static text'), // Never rebuilds
  );
}

4. Missing Dispose

Bad:
class _State extends State<MyWidget> {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(...);
  }
  // Missing dispose - memory leak!
}
Good:
class _State extends State<MyWidget> {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(...);
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }
}

Tools

Static Analysis

# Run analyzer
flutter analyze

# Fix auto-fixable issues
dart fix --apply

# Format code
dart format .

Performance Profiling

# Build in profile mode
flutter build ios --profile

# Run DevTools
flutter pub global activate devtools
flutter pub global run devtools

Code Coverage

# Run tests with coverage
flutter test --coverage

# Generate HTML report
genhtml coverage/lcov.info -o coverage/html
open coverage/html/index.html

Resources


Last Updated: 2025-11-15 Maintained By: Frontend Developer