Created centralized userFriendlyError() utility that converts raw
exceptions into clean, user-friendly messages. Strips hostnames,
ports, OS error codes, HTTP status codes, stack traces, and Django
field names. Maps network/timeout/auth/server errors to plain
English messages.
Fixed 16 locations across 10 files:
- home_screen, calendar_screen, learn_more_screen (SnackBar/Text)
- login_screen, desktop_login_screen (SnackBar)
- profile_screen, contribute_screen, search_screen (SnackBar)
- review_form, review_section (inline error text)
- gamification_provider (error field)
Also removed double-wrapped exceptions in ReviewService (rethrow
instead of throw Exception('Failed to...: $e')).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
74 lines
2.4 KiB
Dart
74 lines
2.4 KiB
Dart
// lib/core/utils/error_utils.dart
|
|
|
|
/// Converts raw exceptions into user-friendly messages.
|
|
/// Strips technical details (hostnames, ports, stack traces, exception chains)
|
|
/// and returns a clean message safe to display in the UI.
|
|
String userFriendlyError(Object e) {
|
|
final raw = e.toString();
|
|
|
|
// Network / connectivity issues
|
|
if (raw.contains('SocketException') ||
|
|
raw.contains('Connection refused') ||
|
|
raw.contains('Connection reset') ||
|
|
raw.contains('Network is unreachable') ||
|
|
raw.contains('No address associated') ||
|
|
raw.contains('Failed to fetch') ||
|
|
raw.contains('HandshakeException') ||
|
|
raw.contains('ClientException')) {
|
|
return 'Unable to connect. Please check your internet connection and try again.';
|
|
}
|
|
|
|
// Timeout
|
|
if (raw.contains('TimeoutException') || raw.contains('timed out')) {
|
|
return 'The request took too long. Please try again.';
|
|
}
|
|
|
|
// Rate limited
|
|
if (raw.contains('status 429') || raw.contains('throttled') || raw.contains('Too Many Requests')) {
|
|
return 'Too many requests. Please wait a moment and try again.';
|
|
}
|
|
|
|
// Auth expired / forbidden
|
|
if (raw.contains('status 401') || raw.contains('Unauthorized')) {
|
|
return 'Session expired. Please log in again.';
|
|
}
|
|
if (raw.contains('status 403') || raw.contains('Forbidden')) {
|
|
return 'You do not have permission to perform this action.';
|
|
}
|
|
|
|
// Server error
|
|
if (RegExp(r'status 5\d\d').hasMatch(raw)) {
|
|
return 'Something went wrong on our end. Please try again later.';
|
|
}
|
|
|
|
// Not found
|
|
if (raw.contains('status 404') || raw.contains('Not Found')) {
|
|
return 'The requested resource was not found.';
|
|
}
|
|
|
|
// Strip Exception wrappers and nested chains for validation messages
|
|
var cleaned = raw
|
|
.replaceAll(RegExp(r'Exception:\s*'), '')
|
|
.replaceAll(RegExp(r'Failed to \w+ \w+:\s*'), '')
|
|
.replaceAll(RegExp(r'Network error:\s*'), '')
|
|
.replaceAll(RegExp(r'Request failed \(status \d+\)\s*'), '')
|
|
.trim();
|
|
|
|
// If the cleaned message is empty or still looks technical, use a generic fallback
|
|
if (cleaned.isEmpty ||
|
|
cleaned.contains('errno') ||
|
|
cleaned.contains('address =') ||
|
|
cleaned.contains('port =') ||
|
|
cleaned.startsWith('{') ||
|
|
cleaned.startsWith('[')) {
|
|
return 'Something went wrong. Please try again.';
|
|
}
|
|
|
|
// Capitalize first letter
|
|
if (cleaned.isNotEmpty) {
|
|
cleaned = cleaned[0].toUpperCase() + cleaned.substring(1);
|
|
}
|
|
|
|
return cleaned;
|
|
}
|