175 lines
4.8 KiB
Dart
175 lines
4.8 KiB
Dart
import 'dart:async';
|
|
import 'package:flutter/foundation.dart';
|
|
import 'package:flutter/material.dart';
|
|
import 'app_logger.dart';
|
|
import 'exceptions.dart';
|
|
|
|
/// Global error handler for the application
|
|
class ErrorHandler {
|
|
static final ErrorHandler _instance = ErrorHandler._internal();
|
|
factory ErrorHandler() => _instance;
|
|
ErrorHandler._internal();
|
|
|
|
final AppLogger _logger = AppLogger();
|
|
bool _isInitialized = false;
|
|
|
|
/// Initialize global error handling
|
|
void initialize() {
|
|
if (_isInitialized) return;
|
|
|
|
// Capture Flutter framework errors
|
|
FlutterError.onError = (FlutterErrorDetails details) {
|
|
_handleFlutterError(details);
|
|
};
|
|
|
|
// Capture errors in platform-specific code
|
|
PlatformDispatcher.instance.onError = (error, stack) {
|
|
_handlePlatformError(error, stack);
|
|
return true;
|
|
};
|
|
|
|
_isInitialized = true;
|
|
_logger.info('Global error handler initialized');
|
|
}
|
|
|
|
/// Run the app with error zone guarding
|
|
static Future<void> runAppWithErrorHandling(
|
|
Future<void> Function() appRunner,
|
|
) async {
|
|
final errorHandler = ErrorHandler();
|
|
errorHandler.initialize();
|
|
|
|
// Run app in a guarded zone to catch async errors
|
|
await runZonedGuarded(
|
|
() async {
|
|
await appRunner();
|
|
},
|
|
(error, stackTrace) {
|
|
errorHandler._handleZoneError(error, stackTrace);
|
|
},
|
|
);
|
|
}
|
|
|
|
/// Handle Flutter framework errors
|
|
void _handleFlutterError(FlutterErrorDetails details) {
|
|
// Log the error
|
|
_logger.error(
|
|
'Flutter Error',
|
|
error: details.exception,
|
|
stackTrace: details.stack,
|
|
);
|
|
|
|
// In debug mode, show the red error screen
|
|
if (kDebugMode) {
|
|
FlutterError.presentError(details);
|
|
}
|
|
|
|
// Optionally report to crash reporting service
|
|
_reportError(details.exception, details.stack);
|
|
}
|
|
|
|
/// Handle platform-specific errors
|
|
bool _handlePlatformError(Object error, StackTrace stackTrace) {
|
|
_logger.error('Platform Error', error: error, stackTrace: stackTrace);
|
|
|
|
_reportError(error, stackTrace);
|
|
return true;
|
|
}
|
|
|
|
/// Handle errors from runZonedGuarded
|
|
void _handleZoneError(Object error, StackTrace stackTrace) {
|
|
_logger.error('Async Error', error: error, stackTrace: stackTrace);
|
|
|
|
_reportError(error, stackTrace);
|
|
}
|
|
|
|
/// Report error to external service (optional)
|
|
void _reportError(Object error, StackTrace? stackTrace) {
|
|
// In production, you could send errors to services like:
|
|
// - Firebase Crashlytics
|
|
// - Sentry
|
|
// - Custom error reporting endpoint
|
|
|
|
if (kReleaseMode) {
|
|
// TODO: Implement error reporting to external service
|
|
// Example: FirebaseCrashlytics.instance.recordError(error, stackTrace);
|
|
}
|
|
}
|
|
|
|
/// Handle and display user-friendly error messages
|
|
static String getUserFriendlyMessage(Object error) {
|
|
if (error is AppException) {
|
|
return error.message;
|
|
} else if (error is NetworkException) {
|
|
return '网络连接失败,请检查网络设置';
|
|
} else if (error is AuthenticationException) {
|
|
return '登录失败,请检查账号密码';
|
|
} else if (error is ParseException) {
|
|
return '数据解析失败,请稍后重试';
|
|
} else if (error is ValidationException) {
|
|
return '输入验证失败:${error.message}';
|
|
} else if (error is TimeoutException) {
|
|
return '请求超时,请稍后重试';
|
|
} else if (error is FormatException) {
|
|
return '数据格式错误';
|
|
} else {
|
|
return '发生未知错误,请稍后重试';
|
|
}
|
|
}
|
|
|
|
/// Show error dialog to user
|
|
static void showErrorDialog(
|
|
BuildContext context,
|
|
Object error, {
|
|
VoidCallback? onRetry,
|
|
}) {
|
|
final message = getUserFriendlyMessage(error);
|
|
|
|
showDialog(
|
|
context: context,
|
|
builder: (context) => AlertDialog(
|
|
title: const Text('错误'),
|
|
content: Text(message),
|
|
actions: [
|
|
if (onRetry != null)
|
|
TextButton(
|
|
onPressed: () {
|
|
Navigator.of(context).pop();
|
|
onRetry();
|
|
},
|
|
child: const Text('重试'),
|
|
),
|
|
TextButton(
|
|
onPressed: () => Navigator.of(context).pop(),
|
|
child: const Text('确定'),
|
|
),
|
|
],
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Show error snackbar to user
|
|
static void showErrorSnackBar(
|
|
BuildContext context,
|
|
Object error, {
|
|
Duration duration = const Duration(seconds: 3),
|
|
}) {
|
|
final message = getUserFriendlyMessage(error);
|
|
|
|
ScaffoldMessenger.of(context).showSnackBar(
|
|
SnackBar(
|
|
content: Text(message),
|
|
duration: duration,
|
|
backgroundColor: Colors.red,
|
|
action: SnackBarAction(
|
|
label: '关闭',
|
|
textColor: Colors.white,
|
|
onPressed: () {
|
|
ScaffoldMessenger.of(context).hideCurrentSnackBar();
|
|
},
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|