feat: Centralized Error Handling & Crash Fixes
This commit is contained in:
@@ -48,54 +48,95 @@ graph TD
|
||||
```
|
||||
|
||||
### 1. The Data Layer (The Guard)
|
||||
* **Role:** Wraps all API calls.
|
||||
* **Mechanism:** Catches raw errors (SocketException, FirebaseException) and converts them into domain-specific `AppExceptions` (e.g., `NetworkException`).
|
||||
* **Location:** `packages/data_connect/lib/src/mixins/data_error_handler.dart`
|
||||
**Location:** `packages/data_connect/lib/src/mixins/data_error_handler.dart`
|
||||
|
||||
### 2. The BLoC Layer (The Logic)
|
||||
* **Role:** Manages state.
|
||||
* **Mechanism:** Uses `handleError` helper to execute logic. If an `AppException` bubbles up, it automatically emits a failure state with a **Message Key** (e.g., `errors.auth.session_expired`).
|
||||
* **Location:** `packages/core/lib/src/presentation/mixins/bloc_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.
|
||||
|
||||
### 3. The UI Layer (The Presenter)
|
||||
* **Role:** Shows the message.
|
||||
* **Mechanism:** Observes the state. When an error occurs, it passes the key to `translateErrorKey(key)` which returns the user-friendly text from the active language file (e.g., `es.i18n.json`).
|
||||
* **Location:** `packages/core_localization/lib/src/utils/error_translator.dart`
|
||||
**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. Verified Features
|
||||
The following features have been audited and are confirmed to use the Centralized Error Handler:
|
||||
## 3. Real-World Example: Submitting a Tax Form
|
||||
|
||||
### **✅ Core Infrastructure**
|
||||
* Authentication (Login/Signup)
|
||||
* Firebase Data Connect Integration
|
||||
* Localization Engine
|
||||
Let's trace what happens when a user submits Form W-4 with no internet:
|
||||
|
||||
### **✅ Staff App Features**
|
||||
| Feature | Status | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **Shifts** | ✅ | Handles availability, accept, history errors |
|
||||
| **Profile** | ✅ | Personal info, experience, attire updates |
|
||||
| **Onboarding** | ✅ | Tax forms (W4/I9), emergency contacts |
|
||||
| **Compliance** | ✅ | Document uploads, certificate management |
|
||||
| **Financials** | ✅ | Bank accounts, time cards, payments |
|
||||
| **Availability** | ✅ | Weekly schedule blocking |
|
||||
| **Clock In** | ✅ | Location/Time validation errors |
|
||||
|
||||
### **✅ Client App Features**
|
||||
| Feature | Status | Notes |
|
||||
| :--- | :---: | :--- |
|
||||
| **Auth** | ✅ | Login, Signup failures |
|
||||
| **Create Order** | ✅ | Type selection, validation |
|
||||
| **View Orders** | ✅ | Loading lists, filtering |
|
||||
| **Hubs** | ✅ | NFC assignment errors |
|
||||
| **Billing** | ✅ | Payment methods, invoicing |
|
||||
| **Coverage** | ✅ | Dashboard metric loading |
|
||||
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. How to Verify (Demo Script)
|
||||
## 4. Simple Verification Tests (For Non-Developers)
|
||||
|
||||
### Test A: The "Tunnel" Test (Network)
|
||||
1. Open the app to the **Shifts** page.
|
||||
@@ -123,7 +164,193 @@ The following features have been audited and are confirmed to use the Centralize
|
||||
|
||||
---
|
||||
|
||||
## 5. Code Locations (Reference)
|
||||
## 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`
|
||||
|
||||
Reference in New Issue
Block a user