50 Error Handling Design
I’ll help you design a robust error handling strategy for your Flutter application with a clean architectural approach.
50.1 Error Handling Architecture Overview
Here’s a conceptual diagram of how errors should flow through your application layers:
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ UI Layer │ │ Service Layer │ │ SDK/HTTP │
│ (Widgets) │ │ (Business Logic)│ │ (Data Source) │
├─────────────────┤ ├──────────────────┤ ├─────────────────┤
│ • User feedback │◄───│ • Error mapping │◄───│ • Network errors│
│ • Loading states│ │ • Retry logic │ │ • API errors │
│ • Error dialogs │ │ • Caching │ │ • Timeouts │
└─────────────────┘ └──────────────────┘ └─────────────────┘
▲ ▲ ▲
│ │ │
Log UI Log Business Log Technical
Interactions Operations Details
50.2 Error Categories & Handling Strategy
Let me break down the approach by layer:
50.2.1 1. SDK/HTTP Layer (Data Source)
What to handle:
- Network connectivity issues
- HTTP status codes (400, 401, 404, 500, etc.)
- Timeout errors
- Parsing/serialization errors
What to log:
class ApiException implements Exception {
final int? statusCode;
final String message;
final String? endpoint;
final DateTime timestamp;
{
ApiException(this.statusCode,
this.message,
required this.endpoint,
}) : timestamp = DateTime.now();
}
class HttpClient {
Future<T> request<T>(String endpoint) async {
try {
final response = await http.get(Uri.parse(endpoint));
// Log technical details
.info('API Request: $endpoint - ${response.statusCode}');
logger
if (response.statusCode >= 400) {
// Log error with full technical context
.error(
logger'API Error: $endpoint',
: 'Status: ${response.statusCode}, Body: ${response.body}',
error
);
throw ApiException(
: response.statusCode,
statusCode: _mapHttpErrorMessage(response.statusCode),
message: endpoint,
endpoint
);}
return _parseResponse<T>(response.body);
} on SocketException {
.error('Network connectivity error: $endpoint');
loggerthrow ApiException(message: 'No internet connection');
} on TimeoutException {
.error('Request timeout: $endpoint');
loggerthrow ApiException(message: 'Request timed out');
}
}
}
50.2.2 2. Service Layer (Business Logic)
What to handle:
- Transform technical errors into business-friendly errors
- Implement retry mechanisms
- Handle caching fallbacks
- Business rule validation
What to log:
abstract class AppException implements Exception {
final String userMessage;
final String? technicalMessage;
final DateTime timestamp;
{
AppException(this.userMessage,
required this.technicalMessage,
}) : timestamp = DateTime.now();
}
class NetworkException extends AppException {
{String? technical})
NetworkException(: super(
: 'Please check your internet connection',
userMessage: technical,
technicalMessage
);}
class DataService {
Future<List<Patient>> getPatients() async {
try {
final patients = await _httpClient.request<List<Patient>>('/patients');
// Log successful business operation
.info('Successfully retrieved ${patients.length} patients');
logger
return patients;
} on ApiException catch (e) {
// Log business context
.warning(
logger'Failed to retrieve patients',
: 'API Error: ${e.statusCode} - ${e.message}',
error
);
// Transform to user-friendly error
if (e.statusCode == 401) {
throw AuthenticationException();
} else if (e.statusCode == null) {
throw NetworkException(technical: e.message);
} else {
throw ServerException(technical: e.message);
}
}
}
}
50.2.3 3. UI Layer (Presentation)
What to handle:
- Display user-friendly error messages
- Show loading states
- Provide retry options
- Navigate based on error type
What to log:
class PatientListScreen extends StatefulWidget {
@override
=> _PatientListScreenState();
_PatientListScreenState createState() }
class _PatientListScreenState extends State<PatientListScreen> {
Future<void> _loadPatients() async {
try {
=> _isLoading = true);
setState(()
final patients = await _dataService.getPatients();
{
setState(() = patients;
_patients = null;
_error });
// Log user interaction success
.info('User successfully loaded patient list');
logger
} on AppException catch (e) {
// Log user-facing error
.info('User encountered error: ${e.userMessage}');
logger
=> _error = e.userMessage);
setState(()
// Show user-friendly error
if (e is AuthenticationException) {
.pushReplacementNamed(context, '/login');
Navigator} else {
.userMessage);
_showErrorSnackbar(e}
} finally {
=> _isLoading = false);
setState(() }
}
}
50.3 Logging Strategy
50.3.1 What to Log at Each Layer:
SDK Layer:
- All HTTP requests/responses (status codes, endpoints)
- Network failures with technical details
- Parsing errors with data structure info
Service Layer:
- Business operation outcomes (success/failure counts)
- Data transformation issues
- Cache hits/misses
- Retry attempts
UI Layer:
- User interaction patterns
- Error presentation to users
- Navigation flows triggered by errors
50.3.2 Sample Logging Setup:
import 'package:logger/logger.dart';
class AppLogger {
static final Logger _logger = Logger(
: PrettyPrinter(
printer: 2,
methodCount: 8,
errorMethodCount: 120,
lineLength: true,
colors: true,
printEmojis,
)
);
static void info(String message, {dynamic error}) {
.i(message, error: error);
_logger}
static void warning(String message, {dynamic error}) {
.w(message, error: error);
_logger}
static void error(String message, {dynamic error, StackTrace? stackTrace}) {
.e(message, error: error, stackTrace: stackTrace);
_logger}
}
50.4 Key Principles for Your Medical App Context:
Patient Data Safety:
- Never log sensitive patient information
- Use anonymized identifiers in logs
- Implement log retention policies
User Experience:
- Provide clear, non-technical error messages
- Offer actionable solutions (“Try again”, “Check connection”)
- Handle authentication errors gracefully
Monitoring:
- Track error patterns for system health
- Monitor API performance metrics
- Set up alerts for critical failures
This layered approach ensures technical details stay in logs while users see helpful messages, making your radiology application both robust and user-friendly.