2. Code Quality & Architecture

Code Quality Metrics

Error Handling Consistency60 / 100
Code Reusability55 / 100
State Management Quality70 / 100

2.1 Code Structure & Organization

Status: ⚠️Warning

Findings:

  • Project uses GitFlow-like branching strategy with multiple long-lived branches
  • Multiple branches exist: master, dev, staging which creates complexity
  • Hard to identify pending changes and what's currently deployed
  • Multiple long-lived branches increase merge conflict risk and slow down development velocity
  • No clear single source of truth for current production code
  • Well-organized code structure with clear separation of concerns (positive aspect)
  • Routes separated from route models (MVVM-like pattern)
  • Clear separation between lib/routes/, lib/route_models/, lib/states/, lib/singletons/, lib/widgets/
  • Database layer properly abstracted with DAOs

Evidence:

  • Git branches: master, dev, staging branches exist
  • Multiple long-lived branches make code review and tracking difficult
  • Clear directory structure:
    • lib/routes/ - UI/Route components
    • lib/route_models/ - ViewModels/Route models
    • lib/states/ - State management classes
    • lib/singletons/ - Global singleton instances
    • lib/database/ - Database layer with DAOs
    • lib/widgets/ - Reusable widgets
  • MVVM-like pattern with route models handling business logic

Risk Level: Medium Risk

Recommendation:

  • Migrate to trunk-based development (main/master as primary branch)
  • Use short-lived feature branches that merge directly to main
  • Implement feature flags for incomplete features instead of long-lived branches
  • Finish and merge all hanging changes from existing feature branches
  • Simplify workflow: main → feature branch → PR → main → deploy
  • Use branch protection rules to enforce code review before merging to main
  • Consider documenting the architecture pattern in README
  • Consider using dependency injection instead of singletons for better testability

2.2 Error Handling

Status: ⚠️Warning

Findings:

  • Error handling is inconsistent across routes
  • Some routes use .catchError() (login_route, password_reset_verify_route, gas_receipt_route, account_four_route)
  • Some routes use .onError() (register_four_route)
  • Some async operations may lack proper error handling
  • Error messages hardcoded in Dutch, not internationalized
  • Generic error messages don't provide specific feedback to users
  • Error handling in Dio interceptor (token refresh) logs errors but may not provide user feedback
  • Some error cases show generic dialogs without specific error information

Evidence:

  • lib/routes/login_route.dart: Uses .catchError() with generic error dialog (lines 287-300)
    • Error message: "Er ging iets mis" (hardcoded Dutch)
  • lib/routes/register_four_route.dart: Uses .onError() (line 353)
  • lib/routes/password_reset_verify_route.dart: Uses .catchError() (line 302)
  • lib/routes/gas_receipt_route.dart: Uses .catchError() (line 362)
  • lib/routes/account_four_route.dart: Uses .catchError() in multiple places (lines 394, 562)
  • lib/singletons/dio_singleton.dart: Token refresh error handling (lines 89-92) only resets session, doesn't notify user
  • Error messages hardcoded: "Er ging iets mis", "Controleer je gebruikersnaam en wachtwoord"
  • No centralized error handling helper function

Risk Level: Medium Risk

Recommendation:

  • Immediate actions:
    • Create centralized error handling helper function
    • Standardize error handling across all routes (use consistent pattern)
    • Add error handling to all async operations (ensure all .then() have error handlers)
    • Internationalize error messages
  • Short-term:
    • Create error handling base class or mixin for routes
    • Add specific error types and user-friendly messages
    • Implement retry mechanisms for transient errors
    • Add error boundaries for widget tree errors
    • Improve user feedback in Dio interceptor error handling

2.3 State Management

Status: ⚠️Warning

Findings:

  • Provider pattern with ChangeNotifier implemented for state management
  • Multiple state classes follow similar patterns: SessionState, ConnectivityState, SavingState, FavoriteGasStationState, SelectedGasStationMarker, RegisterState
  • Singleton pattern used for global state access
  • State synchronization issues: State stored in both memory and database, potential for inconsistency
  • Direct database access in some places bypasses state management layer
  • Async state initialization pattern (init() method) used in SessionState
  • State persistence to database happens on every state change (potential performance issue)
  • RegisterState doesn't use ChangeNotifier (inconsistent pattern)
  • Global singleton access used throughout codebase

Evidence:

  • lib/states/session_state.dart: Uses ChangeNotifier, stores state in both memory and database
    • Every setter saves to database immediately (lines 58-97)
    • Async init() method loads from database (lines 23-56)
  • lib/states/connectivity_state.dart: Uses ChangeNotifier pattern
  • lib/states/favorite_gas_station_state.dart: Uses ChangeNotifier
  • lib/states/saving_state.dart: Uses ChangeNotifier
  • lib/states/register_state.dart: Does NOT use ChangeNotifier (inconsistent)
  • lib/singletons/: Multiple singletons for global state access
    • SessionStateSingleton, RegisterStateSingleton, ConnectivityStateSingleton, DatabaseSingleton, DioSingleton, FirebaseSingleton, NavigatorSingleton
  • State initialization: sessionState.init() called separately (async pattern)
  • Direct database access: lib/database/daos/ accessed directly in some places

Risk Level: Medium Risk

Recommendation:

  • Immediate actions:
    • Review state synchronization between memory and database
    • Add state validation to prevent inconsistencies
    • Make RegisterState consistent with other state classes (use ChangeNotifier)
    • Consider using flutter_secure_storage for sensitive state instead of database
  • Short-term:
    • Implement state management best practices (single source of truth)
    • Add state change notifications and observers
    • Consider using dependency injection instead of singletons for better testability
    • Separate UI state from business logic state
    • Consider batching database writes for performance

2.4 Code Duplication

Status: ⚠️Warning

Findings:

  • Firebase Performance tracing duplicated: Similar tracing code duplicated across all routes (30+ routes)
  • Route setup pattern repeated: Similar setup() async method pattern in multiple routes
  • FutureBuilder pattern duplicated: Similar FutureBuilder usage across routes for loading states
  • Singleton access pattern: Similar singleton instantiation patterns across routes
  • Error handling duplicated: Similar error handling code in multiple routes
  • Dialog patterns repeated: Similar AlertDialog creation code in multiple places
  • Session state checks: Similar session state access patterns throughout routes
  • API call patterns: Similar .then().catchError() patterns repeated across routes

Evidence:

  • Firebase Performance tracing duplicated in 30+ routes:
    • lib/routes/login_route.dart (lines 47-52)
    • lib/routes/generic_route.dart (lines 29-34)
    • lib/routes/webview_route.dart (lines 37-42)
    • And many more routes with identical pattern
  • Similar setup() async method pattern:
    • lib/routes/login_route.dart (lines 57-70)
    • lib/routes/generic_route.dart (lines 45-49)
    • lib/routes/register_one_route.dart, register_three_route.dart, register_four_route.dart
    • Multiple routes follow same pattern
  • Similar FutureBuilder patterns for loading states across routes
  • Singleton access: SessionStateSingleton(), RegisterStateSingleton(), etc. instantiated in multiple routes
  • Error handling: Similar .catchError() with AlertDialog patterns in multiple routes

Risk Level: Medium Risk

Recommendation:

  • Immediate actions:
    • Extract Firebase Performance tracing into a base route class or mixin
    • Create reusable widgets for common UI patterns (loading states, error states)
    • Extract common route patterns into base classes or mixins
    • Create base route class with common functionality (setup(), tracing, etc.)
  • Short-term:
    • Refactor state management classes to use inheritance or composition
    • Extract common API call patterns into service classes
    • Create reusable error handling widgets
    • Implement route factory pattern for common route structures
    • Create mixin for Firebase Performance tracing

2.5 Dead Code & Unused Files

Status: ⚠️Warning

Findings:

  • Deprecated route file present in repository
  • lib/routes/register_two_route_old.dart is marked as @deprecated but still in codebase
  • Old route file may cause confusion and should be removed if no longer used
  • Repository contains potentially unused code that should be audited

Evidence:

  • lib/routes/register_two_route_old.dart: Marked with @deprecated annotation (line 15)
  • File still exists in repository but may not be referenced
  • Similar pattern may exist in other files

Risk Level: Low Risk

Recommendation:

  • Remove lib/routes/register_two_route_old.dart if no longer used
  • Audit repository for other deprecated or unused files
  • Establish process for removing obsolete files during refactoring
  • Keep repository clean by removing dead code
  • Use deprecation warnings in code to track removal timeline

2.6 Best Practices & Patterns

Status: ⚠️Warning

Findings:

  • Singleton pattern used extensively, which can make testing difficult
  • Direct database access in some places bypasses state management
  • Inconsistent state management patterns (RegisterState doesn't use ChangeNotifier)
  • No clear dependency injection strategy
  • Mix of patterns (singletons, Provider, direct database access) creates complexity
  • Need strategic approach to refactoring for better testability and maintainability

Evidence:

  • Multiple singleton classes in lib/singletons/
  • Direct database access in routes and models
  • Inconsistent state management (most use ChangeNotifier, RegisterState doesn't)
  • No dependency injection framework in use
  • Global state access throughout codebase

Risk Level: Medium Risk

Recommendation:

  • Strategic Approach: Consider migrating from singletons to dependency injection
  • Align state management patterns (make all state classes consistent)
  • Create clear architecture guidelines for new code
  • Consider using a DI framework (e.g., get_it) for better testability
  • Document architectural decisions and patterns
  • Plan refactoring sprints to improve testability and maintainability
  • Separate concerns: business logic, state management, and UI layers