359 lines
13 KiB
Markdown
359 lines
13 KiB
Markdown
# 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<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:**
|
|
```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<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):**
|
|
```dart
|
|
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:
|
|
|
|
```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`
|