← Назад к вопросам

Как обрабатывать ошибки во 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.

Как обрабатывать ошибки во Flutter? | PrepBro