refactor: improve Dio error parsing and add user-friendly fallback messages for network and status code failures

This commit is contained in:
pradeepkumar
2026-04-09 05:22:11 +05:30
parent 509eae2e88
commit f9c6d5e5da

View File

@@ -231,40 +231,94 @@ class ApiClient {
/// Wrap a DioException with parsed error data. /// Wrap a DioException with parsed error data.
DioException _wrapError(DioException error) { DioException _wrapError(DioException error) {
final response = error.response; final response = error.response;
// 1. Try to parse a structured error from the JSON response body
if (response != null) { if (response != null) {
final data = response.data; final data = response.data;
if (data is Map<String, dynamic>) { if (data is Map<String, dynamic>) {
final message = data['message'] is List final rawMessage = data['message'] ?? data['error'];
? (data['message'] as List).join(', ') final message = rawMessage is List
: data['message']?.toString() ?? 'An error occurred'; ? (rawMessage).join(', ')
: rawMessage?.toString();
final errors = (data['errors'] as List<dynamic>?) if (message != null && message.isNotEmpty) {
?.map( final errors = (data['errors'] as List<dynamic>?)
(e) => FieldError.fromJson(e as Map<String, dynamic>)) ?.map((e) => FieldError.fromJson(e as Map<String, dynamic>))
.toList() ?? .toList() ??
[]; [];
return DioException( return DioException(
requestOptions: error.requestOptions, requestOptions: error.requestOptions,
response: error.response, response: error.response,
error: ApiException( error: ApiException(
message: message, message: message,
statusCode: response.statusCode, statusCode: response.statusCode,
fieldErrors: errors, fieldErrors: errors,
), ),
); );
}
} }
} }
// 2. Fallback — build a user-friendly message from the status code or
// Dio exception type. NEVER use `error.message` because Dio emits a
// giant technical blob ("This exception was thrown because the
// response has a status code of 401 and RequestOptions.validateStatus
// was configured to throw...") which is useless to end users.
return DioException( return DioException(
requestOptions: error.requestOptions, requestOptions: error.requestOptions,
response: error.response, response: error.response,
error: ApiException( error: ApiException(
message: error.message ?? 'Network error occurred', message: _friendlyMessageFor(error),
statusCode: response?.statusCode, statusCode: response?.statusCode,
), ),
); );
} }
String _friendlyMessageFor(DioException error) {
// Network-level failures take priority over status codes
switch (error.type) {
case DioExceptionType.connectionTimeout:
case DioExceptionType.sendTimeout:
case DioExceptionType.receiveTimeout:
return 'The request timed out. Please check your connection and try again.';
case DioExceptionType.connectionError:
return 'Unable to reach the server. Please check your internet connection.';
case DioExceptionType.cancel:
return 'Request was cancelled.';
case DioExceptionType.badCertificate:
return 'Secure connection failed. Please try again later.';
case DioExceptionType.badResponse:
case DioExceptionType.unknown:
break;
}
// Status-code-based fallback
final code = error.response?.statusCode;
switch (code) {
case 400:
return 'The request was invalid. Please check your input.';
case 401:
return 'Invalid email or password.';
case 403:
return 'You don\'t have permission to perform this action.';
case 404:
return 'The requested resource was not found.';
case 409:
return 'This action conflicts with existing data.';
case 422:
return 'Some fields are invalid. Please check your input.';
case 429:
return 'Too many attempts. Please wait a moment and try again.';
case 500:
case 502:
case 503:
case 504:
return 'Server is temporarily unavailable. Please try again shortly.';
}
return 'Something went wrong. Please try again.';
}
} }
class _QueuedRequest { class _QueuedRequest {