← Назад к вопросам
Как обрабатывать ошибки во Flutter?
2.0 Middle🔥 192 комментариев
#Архитектура Flutter#Тестирование
Комментарии (2)
🐱
claude-haiku-4.5PrepBro AI29 мар. 2026 г.(ред.)
Ответ сгенерирован нейросетью и может содержать ошибки
Обработка ошибок во Flutter
Обработка ошибок — критически важная часть разработки. Flutter предоставляет несколько механизмов для безопасной работы с исключениями, асинхронными ошибками и состояниями отказа.
1. Try-Catch — базовая обработка
Используется для синхронного и асинхронного кода.
// Синхронный код
try {
final result = int.parse('not a number');
} catch (e) {
print('Ошибка: $e');
}
// Асинхронный код
try {
final response = await http.get(Uri.parse('https://api.example.com/data'));
if (response.statusCode == 200) {
final data = jsonDecode(response.body);
} else {
throw HttpException('Status ${response.statusCode}');
}
} catch (e) {
print('Ошибка API: $e');
} finally {
// Выполнится в любом случае
print('Завершено');
}
2. Специфичные типы исключений
Лучше ловить конкретные типы ошибок:
try {
final data = await fetchData();
} on SocketException {
print('Проблемы с сетью');
} on TimeoutException {
print('Запрос истёк по времени');
} on FormatException {
print('Ошибка парсинга JSON');
} catch (e) {
print('Неожиданная ошибка: $e');
}
3. Пользовательские исключения
Создавайте свои типы ошибок для лучшей обработки:
// Определение
abstract class AppException implements Exception {
final String message;
AppException(this.message);
}
class NetworkException extends AppException {
NetworkException(String message) : super(message);
}
class ValidationException extends AppException {
ValidationException(String message) : super(message);
}
class ServerException extends AppException {
final int statusCode;
ServerException(this.statusCode, String message) : super(message);
}
// Использование
Future<User> getUser(String id) async {
try {
final response = await http.get(Uri.parse('https://api.example.com/users/$id'));
if (response.statusCode == 200) {
return User.fromJson(jsonDecode(response.body));
} else if (response.statusCode == 404) {
throw ServerException(404, 'User not found');
} else {
throw ServerException(response.statusCode, 'Server error');
}
} on SocketException {
throw NetworkException('No internet connection');
} on TimeoutException {
throw NetworkException('Request timeout');
} on FormatException {
throw ValidationException('Invalid JSON format');
}
}
4. Result паттерн — функциональный подход
Вместо выбросов исключений можно возвращать Result с успехом или ошибкой:
// Определение
abstract class Result<T> {
R map<R>(
R Function(T) onSuccess,
R Function(Exception) onError,
);
}
class Success<T> extends Result<T> {
final T data;
Success(this.data);
@override
R map<R>(R Function(T) onSuccess, R Function(Exception) onError) {
return onSuccess(data);
}
}
class Error<T> extends Result<T> {
final Exception exception;
Error(this.exception);
@override
R map<R>(R Function(T) onSuccess, R Function(Exception) onError) {
return onError(exception);
}
}
// Использование
Future<Result<User>> getUser(String id) async {
try {
final response = await http.get(Uri.parse('https://api.example.com/users/$id'));
if (response.statusCode == 200) {
return Success(User.fromJson(jsonDecode(response.body)));
} else {
return Error(ServerException(response.statusCode, 'Error'));
}
} catch (e) {
return Error(e as Exception);
}
}
// Обработка
final result = await getUser('123');
result.map(
(user) => print('User: ${user.name}'),
(error) => print('Error: ${error.message}'),
);
5. Обработка в UI слое (FutureBuilder)
class UserScreen extends StatelessWidget {
final Future<User> userFuture;
@override
Widget build(BuildContext context) {
return FutureBuilder<User>(
future: userFuture,
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return Center(child: CircularProgressIndicator());
}
if (snapshot.hasError) {
return ErrorWidget(
error: snapshot.error,
onRetry: () {},
);
}
if (snapshot.hasData) {
return UserCard(user: snapshot.data!);
}
return SizedBox.shrink();
},
);
}
}
class ErrorWidget extends StatelessWidget {
final dynamic error;
final VoidCallback onRetry;
@override
Widget build(BuildContext context) {
String message = 'Something went wrong';
if (error is NetworkException) {
message = 'No internet connection';
} else if (error is ServerException) {
message = 'Server error: ${(error as ServerException).statusCode}';
} else if (error is ValidationException) {
message = 'Invalid data';
}
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Icon(Icons.error, size: 48, color: Colors.red),
SizedBox(height: 16),
Text(message),
SizedBox(height: 16),
ElevatedButton(
onPressed: onRetry,
child: Text('Retry'),
),
],
),
);
}
}
6. Глобальная обработка ошибок
void main() {
// Перехватываем необработанные исключения
FlutterError.onError = (FlutterErrorDetails details) {
FlutterError.presentError(details);
// Логируем на сервер
logErrorToServer(details);
};
// Асинхронные ошибки
PlatformDispatcher.instance.onError = (error, stack) {
print('Unhandled error: $error');
logErrorToServer(error.toString());
return true; // обработано
};
runApp(MyApp());
}
Future<void> logErrorToServer(dynamic error) async {
try {
await http.post(
Uri.parse('https://api.example.com/logs'),
body: {'error': error.toString()},
);
} catch (e) {
// Молча игнорируем, чтобы не создавать бесконечный цикл
print('Failed to log error: $e');
}
}
7. Логирование ошибок
class Logger {
static void logError(
dynamic error,
StackTrace stackTrace, {
String context = '',
}) {
print('''=== ERROR ===
Context: $context
Error: $error
Stack: $stackTrace
''');
// Отправить на Crashlytics
// FirebaseCrashlytics.instance.recordError(error, stackTrace);
}
}
// Использование
try {
await fetchData();
} catch (e, st) {
Logger.logError(e, st, context: 'fetchData');
rethrow; // Пробросить выше если нужно
}
8. Практический пример: Сервис с обработкой ошибок
class UserRepository {
final http.Client httpClient;
UserRepository(this.httpClient);
Future<User> getUser(String id) async {
try {
final response = await httpClient
.get(Uri.parse('https://api.example.com/users/$id'))
.timeout(Duration(seconds: 10));
switch (response.statusCode) {
case 200:
return User.fromJson(jsonDecode(response.body));
case 404:
throw UserNotFoundException('User not found');
case 500:
throw ServerException(500, 'Server error');
default:
throw ServerException(response.statusCode, 'Unknown error');
}
} on SocketException {
throw NetworkException('No internet connection');
} on TimeoutException {
throw NetworkException('Request timeout');
} on FormatException {
throw ValidationException('Invalid response format');
}
}
}
class UserNotFoundException extends AppException {
UserNotFoundException(String message) : super(message);
}
Чеклист обработки ошибок
Для каждой асинхронной операции:
- Есть try-catch блок
- Перехватываются конкретные типы ошибок
- Есть fallback для неожиданных ошибок
- Ошибка логируется или показывается пользователю
- Есть механизм повтора (retry)
На уровне приложения:
- Глобальный обработчик необработанных ошибок
- Логирование на сервер (Crashlytics, Sentry)
- Пользовательское сообщение об ошибке
- Graceful degradation (приложение продолжает работать)
Вывод: Обрабатывай ошибки на каждом уровне: в сервисах (throw), в use case (обработка), в UI (показ). Логируй всё, дай пользователю понятное сообщение, предусмотри retry.