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/catchblocks.
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:
- User Action: Clicks "Submit Form"
- BLoC:
FormW4BloccallssubmitW4UseCase.call(formData) - Use Case: Calls
taxFormsRepository.submitW4(formData) - Repository (Data Layer):
Future<void> submitW4(W4Data data) async { return handleDataOperation(() async { await _api.submitW4(data); // This throws SocketException }); } - DataErrorHandler Mixin: Catches
SocketException→ throwsNetworkException('errors.network.no_connection') - BLoC: Catches
NetworkException→ emitsFormW4State.error(errorMessage: 'errors.network.no_connection') - UI (FormW4Page):
BlocListener<FormW4Bloc, FormW4State>( listener: (context, state) { if (state.status == FormW4Status.error) { UiSnackbar.show( context, message: translateErrorKey(state.errorMessage!), type: UiSnackbarType.error, ); } }, ) - translateErrorKey: Looks up
'errors.network.no_connection'inen.i18n.json→ returns"No internet connection. Please check your network." - User Sees: A friendly snackbar with the localized message
4. Simple Verification Tests (For Non-Developers)
Test A: The "Tunnel" Test (Network)
- Open the app to the Shifts page.
- Toggle Airplane Mode ON.
- Pull to refresh the list.
- Result: App shows a gentle
snackbarerror: "No internet connection" (or Spanish equivalent). No Crash.
Test B: The "Duplicate Data" Test (Smart Validation)
- Log in on two devices with the same account (if possible) or simply use a known registered email.
- Go to the Sign Up page.
- Try to register a new account using that existing email.
- Result: App instantly displays specific, helpful feedback: "An account with this email already exists." instead of a generic failure.
- 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)
- Scenario: Even if a developer introduces a bug (like a random exception) or the server returns a 500 status.
- Result: The app catches the unknown error, logs it internally, and shows a safe default message: "Something went wrong. Please try again."
- Why it matters: The app never crashes or closes unexpectedly, preserving user trust.
Test D: The "Language" Test (Localization)
- Trigger an error (like wrong password).
- Change phone language to Spanish.
- Trigger the same error.
- 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
- Navigate to Profile → Documents → Form W-4
- Try to proceed to next step without filling required fields
- 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
- Fill out Form I-9 completely
- Turn off WiFi/Mobile Data
- Click Submit Form
- 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
- Fill out Form W-4
- If backend is accessible, trigger a 500 error
- 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
- Navigate to Form I-9
- Trigger any error (validation or network)
- Note the error message
- Change device language to Spanish (Settings → Language)
- Trigger the same error
- 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
- Navigate to Shifts tab
- Enable Airplane Mode
- Pull to refresh
- Expected Result:
- Snackbar: "No internet connection"
- No crash or blank screen
- Previous data (if any) remains visible
Test B2: Shift Not Found (404)
- If possible, try to access a deleted/non-existent shift
- Expected Result:
- Snackbar: "Shift not found"
- User is redirected back to shifts list
📱 Test Suite C: Profile & Authentication
Test C1: Session Expiry (401)
- Let the app sit idle for extended period (or manually invalidate token)
- Try to perform any action (update profile, submit form)
- Expected Result:
- Snackbar: "Your session has expired. Please log in again."
- App automatically redirects to login screen
Test C2: Profile Update Errors
- Navigate to Profile → Personal Info
- Try to update with invalid data (e.g., invalid email format)
- Expected Result:
- Validation error in snackbar
- Specific message about what's wrong
📱 Test Suite D: Payments & Bank Account
Test D1: Bank Account Addition Error
- Navigate to Profile → Bank Account
- Try to add account with invalid routing number
- Expected Result:
- Snackbar shows validation error
- Error is localized
Test D2: Payment History Network Error
- Navigate to Payments tab
- Turn off internet
- Try to load payment history
- Expected Result:
- Snackbar: "No internet connection"
- No crash
📱 Test Suite E: Clock In & Attendance
Test E1: Clock In Network Error
- Navigate to Clock In tab
- Disable network
- Try to clock in
- 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:
- No Crashes: App never crashes due to network/server errors
- Consistent Display: All errors appear in
UiSnackbarwith same styling - User-Friendly: Messages are clear, specific, and actionable
- Localized: All errors translate correctly to Spanish
- Graceful Degradation: App remains usable even when errors occur
- Data Preservation: Form data is not lost when errors happen
🐛 What to Report if Tests Fail
If any test fails, please report:
- Which test (e.g., "Test A2: Network Error During Submission")
- What happened (e.g., "App crashed" or "Error showed in English despite Spanish language")
- Screenshot of the error (if visible)
- 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