# 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: ```mermaid 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:** ```dart mixin DataErrorHandler { Future handleDataOperation(Future 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:** ```dart 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:** ```dart 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):** ```dart Future 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):** ```dart BlocListener( 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: ```dart // 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`