Files
Krow-workspace/docs/ERROR_HANDLING_ARCHITECTURE.md

13 KiB

Centralized Error Handling Architecture

Project: KROW Workforce Mobile App

1. Executive Summary

We have implemented a Centralized Error Handling System that ensures the entire application (Staff & Client) handles errors consistently, reliably, and with full localization support.

Instead of writing error handling code in every single feature (which leads to bugs and inconsistent messages), we rely on a global safety net that catches Network Failures, Server Errors (500), Not Found Errors (404), and Business Logic Violations automatically.

Key Benefits

  • Safety: The app never crashes due to unhandled API errors.
  • Consistency: A network error looks the same in "Shifts" as it does in "billing".
  • Localization: All error messages are automatically translated (English/Spanish).
  • Speed: Developers can build features faster without worrying about try/catch blocks.

Technical Excellence (Status Code Handling)

We don't just catch "errors"; we understand them. The system automatically categorizes and handles:

  • 500 (Server Error): "Our servers are having a moment. Please try again."
  • 404 (Not Found): "The resource you're looking for (Shift/Profile) is missing."
  • 401 (Unauthorized): "Your session expired. Please log in again." (Auto-redirect)
  • 403 (Forbidden): "You don't have permission to access this area."
  • 503 (Unavailable): "Maintenance mode or overloaded. Back in a bit!"

2. Architecture Overview

The error handling flows through three distinct layers, ensuring separation of concerns:

graph TD
    A[Data Layer / Repository] -->|Throws AppException| B[BLoC Layer / State Management]
    B -->|Emits Error Key| C[UI Layer / Presentation]
    
    subgraph "1. Data Layer (The Guard)"
    A -- Captures Exceptions --> D[DataErrorHandler Mixin]
    D -- Maps to --> E[NetworkException, ServerException, etc.]
    end
    
    subgraph "2. BLoC Layer (The Translator)"
    B -- Uses --> F[BlocErrorHandler Mixin]
    F -- Catches AppException --> G[converts to 'errors.category.type']
    end
    
    subgraph "3. UI Layer (The Messenger)"
    C -- Calls --> H["translateErrorKey()"]
    H -- Returns --> I["Localized String (e.g. 'Sin conexión')"]
    end

1. The Data Layer (The Guard)

Location: packages/data_connect/lib/src/mixins/data_error_handler.dart

This is where raw exceptions (from Firebase, network, etc.) are caught and converted into typed exceptions that the rest of the app can understand.

Example:

mixin DataErrorHandler {
  Future<T> handleDataOperation<T>(Future<T> Function() operation) async {
    try {
      return await operation();
    } on SocketException {
      throw NetworkException('errors.network.no_connection');
    } on HttpException catch (e) {
      if (e.statusCode == 500) throw ServerException('errors.server.internal');
      if (e.statusCode == 404) throw NotFoundException('errors.not_found.resource');
      // ... more mappings
    }
  }
}

2. The BLoC Layer (The Translator)

Location: packages/core/lib/src/presentation/mixins/bloc_error_handler.dart

BLoCs use this mixin to catch exceptions and convert them into error keys that can be localized.

Example:

mixin BlocErrorHandler {
  String handleError(Object error) {
    if (error is NetworkException) return error.message;
    if (error is ServerException) return error.message;
    return 'errors.unknown';
  }
}

3. The UI Layer (The Messenger)

Location: packages/core_localization/lib/src/utils/error_translator.dart

The UI calls translateErrorKey() to convert error keys into user-friendly, localized messages.

Example:

String translateErrorKey(String key) {
  final t = LocaleSettings.instance.currentTranslations;
  return t[key] ?? 'An error occurred';
}

3. Real-World Example: Submitting a Tax Form

Let's trace what happens when a user submits Form W-4 with no internet:

  1. User Action: Clicks "Submit Form"
  2. BLoC: FormW4Bloc calls submitW4UseCase.call(formData)
  3. Use Case: Calls taxFormsRepository.submitW4(formData)
  4. Repository (Data Layer):
    Future<void> submitW4(W4Data data) async {
      return handleDataOperation(() async {
        await _api.submitW4(data); // This throws SocketException
      });
    }
    
  5. DataErrorHandler Mixin: Catches SocketException → throws NetworkException('errors.network.no_connection')
  6. BLoC: Catches NetworkException → emits FormW4State.error(errorMessage: 'errors.network.no_connection')
  7. UI (FormW4Page):
    BlocListener<FormW4Bloc, FormW4State>(
      listener: (context, state) {
        if (state.status == FormW4Status.error) {
          UiSnackbar.show(
            context,
            message: translateErrorKey(state.errorMessage!),
            type: UiSnackbarType.error,
          );
        }
      },
    )
    
  8. translateErrorKey: Looks up 'errors.network.no_connection' in en.i18n.json → returns "No internet connection. Please check your network."
  9. User Sees: A friendly snackbar with the localized message

4. Simple Verification Tests (For Non-Developers)

Test A: The "Tunnel" Test (Network)

  1. Open the app to the Shifts page.
  2. Toggle Airplane Mode ON.
  3. Pull to refresh the list.
  4. Result: App shows a gentle snackbar error: "No internet connection" (or Spanish equivalent). No Crash.

Test B: The "Duplicate Data" Test (Smart Validation)

  1. Log in on two devices with the same account (if possible) or simply use a known registered email.
  2. Go to the Sign Up page.
  3. Try to register a new account using that existing email.
  4. Result: App instantly displays specific, helpful feedback: "An account with this email already exists." instead of a generic failure.
  5. Why it matters: Proves the backend and frontend are synced to guide the user, not just block them.

Test C: The "Crash Proof" Test (The Safety Net)

  1. Scenario: Even if a developer introduces a bug (like a random exception) or the server returns a 500 status.
  2. Result: The app catches the unknown error, logs it internally, and shows a safe default message: "Something went wrong. Please try again."
  3. Why it matters: The app never crashes or closes unexpectedly, preserving user trust.

Test D: The "Language" Test (Localization)

  1. Trigger an error (like wrong password).
  2. Change phone language to Spanish.
  3. Trigger the same error.
  4. Result: Message automatically translates: "Correo electrónico o contraseña inválidos."

5. Comprehensive Testing Guide for Client Verification

This section provides a complete testing checklist to verify that centralized error handling is working correctly across the entire Staff app.

🎯 Testing Objectives

  • Verify all errors are caught and handled gracefully (no crashes)
  • Confirm error messages are user-friendly and localized
  • Ensure consistent error display using UiSnackbar
  • Validate that error keys are properly translated

📱 Test Suite A: Tax Forms (I-9 & W-4)

These forms have been fully integrated with centralized error handling and localization.

Test A1: Form Validation Errors

  1. Navigate to Profile → Documents → Form W-4
  2. Try to proceed to next step without filling required fields
  3. Expected Result:
    • Validation error appears in a snackbar
    • Message is clear and specific (e.g., "First name is required")
    • Error is localized (Spanish: "Se requiere el nombre")

Test A2: Network Error During Submission

  1. Fill out Form I-9 completely
  2. Turn off WiFi/Mobile Data
  3. Click Submit Form
  4. Expected Result:
    • Snackbar shows: "No internet connection. Please check your network."
    • Spanish: "Sin conexión a internet. Verifica tu red."
    • Form data is NOT lost

Test A3: Server Error Simulation

  1. Fill out Form W-4
  2. If backend is accessible, trigger a 500 error
  3. Expected Result:
    • Snackbar shows: "Our servers are having issues. Please try again."
    • Spanish: "Nuestros servidores tienen problemas. Inténtalo de nuevo."

Test A4: Language Switching

  1. Navigate to Form I-9
  2. Trigger any error (validation or network)
  3. Note the error message
  4. Change device language to Spanish (Settings → Language)
  5. Trigger the same error
  6. Expected Result:
    • Error message appears in Spanish
    • All form labels and hints are also in Spanish

📱 Test Suite B: Shifts & Availability

Test B1: Network Error on Shifts Page

  1. Navigate to Shifts tab
  2. Enable Airplane Mode
  3. Pull to refresh
  4. Expected Result:
    • Snackbar: "No internet connection"
    • No crash or blank screen
    • Previous data (if any) remains visible

Test B2: Shift Not Found (404)

  1. If possible, try to access a deleted/non-existent shift
  2. Expected Result:
    • Snackbar: "Shift not found"
    • User is redirected back to shifts list

📱 Test Suite C: Profile & Authentication

Test C1: Session Expiry (401)

  1. Let the app sit idle for extended period (or manually invalidate token)
  2. Try to perform any action (update profile, submit form)
  3. Expected Result:
    • Snackbar: "Your session has expired. Please log in again."
    • App automatically redirects to login screen

Test C2: Profile Update Errors

  1. Navigate to Profile → Personal Info
  2. Try to update with invalid data (e.g., invalid email format)
  3. Expected Result:
    • Validation error in snackbar
    • Specific message about what's wrong

📱 Test Suite D: Payments & Bank Account

Test D1: Bank Account Addition Error

  1. Navigate to Profile → Bank Account
  2. Try to add account with invalid routing number
  3. Expected Result:
    • Snackbar shows validation error
    • Error is localized

Test D2: Payment History Network Error

  1. Navigate to Payments tab
  2. Turn off internet
  3. Try to load payment history
  4. Expected Result:
    • Snackbar: "No internet connection"
    • No crash

📱 Test Suite E: Clock In & Attendance

Test E1: Clock In Network Error

  1. Navigate to Clock In tab
  2. Disable network
  3. Try to clock in
  4. Expected Result:
    • Snackbar: "No internet connection"
    • Clock in action is blocked until network returns

🌐 Test Suite F: Localization Verification

Test F1: English Error Messages

With device language set to English, verify these error keys translate correctly:

Scenario Expected English Message
No internet "No internet connection. Please check your network."
Server error (500) "Our servers are having issues. Please try again."
Not found (404) "The requested resource was not found."
Unauthorized (401) "Your session has expired. Please log in again."
Validation error Specific field error (e.g., "Email is required")

Test F2: Spanish Error Messages

With device language set to Español, verify these error keys translate correctly:

Scenario Expected Spanish Message
No internet "Sin conexión a internet. Verifica tu red."
Server error (500) "Nuestros servidores tienen problemas. Inténtalo de nuevo."
Not found (404) "No se encontró el recurso solicitado."
Unauthorized (401) "Tu sesión ha expirado. Inicia sesión nuevamente."
Validation error Error específico del campo

Success Criteria

The centralized error handling is working correctly if:

  1. No Crashes: App never crashes due to network/server errors
  2. Consistent Display: All errors appear in UiSnackbar with same styling
  3. User-Friendly: Messages are clear, specific, and actionable
  4. Localized: All errors translate correctly to Spanish
  5. Graceful Degradation: App remains usable even when errors occur
  6. Data Preservation: Form data is not lost when errors happen

🐛 What to Report if Tests Fail

If any test fails, please report:

  1. Which test (e.g., "Test A2: Network Error During Submission")
  2. What happened (e.g., "App crashed" or "Error showed in English despite Spanish language")
  3. Screenshot of the error (if visible)
  4. Steps to reproduce

🔧 Quick Debug Commands

For developers debugging error handling:

// Test error translation directly
print(translateErrorKey('errors.network.no_connection'));
print(translateErrorKey('errors.server.internal'));
print(translateErrorKey('errors.not_found.shift'));

// Test in Spanish
LocaleSettings.setLocale(AppLocale.es);
print(translateErrorKey('errors.network.no_connection'));

6. Code Locations (Reference)

  • Exceptions: packages/domain/lib/src/exceptions/app_exception.dart
  • Data Mixin: packages/data_connect/lib/src/mixins/data_error_handler.dart
  • Bloc Mixin: packages/core/lib/src/presentation/mixins/bloc_error_handler.dart
  • Translator: packages/core_localization/lib/src/utils/error_translator.dart
  • Strings: packages/core_localization/lib/src/l10n/*.i18n.json