Рубрики
Flutter

Отладка Flutter приложений: Полное руководство 2025

Полное руководство по отладке Flutter приложений: DevTools, breakpoints, logging, error handling, crash reporting.

Отладка — важная часть разработки. Разберём инструменты и техники для эффективной отладки Flutter приложений.

Dart DevTools

Запуск DevTools

# Запуск через CLI
flutter pub global run devtools

# Или при запуске приложения
flutter run --profile

Основные функции DevTools

  • Flutter Inspector — исследование виджетов
  • Performance — анализ производительности
  • Memory — профилирование памяти
  • Network — анализ сетевых запросов
  • Logging — просмотр логов

Flutter Inspector

Tree view

<MyApp>
  <MaterialApp>
    <Scaffold>
      <CustomScrollView>
        <SliverAppBar>
        <ListView>

Widget select

// Используйте ключи для идентификации виджетов
Scaffold(
  key: Key('home-scaffold'),
  appBar: AppBar(title: Text('Home')),
)

Property Inspector

Просмотр свойств виджета в реальном времени.

Breakpoints

Установка breakpoints

В VS Code / Android Studio:

void main() {
  runApp(MyApp()); // Кликните слева для breakpoint
}

Conditional breakpoints

int counter = 0;

for (int i = 0; i < 100; i++) {
  counter += i; // Breakpoint когда i == 50
}

Logpoint

Вместо breakpoint добавьте лог:

counter += i; // Logpoint: print('Counter: $counter')

Debugging print statements

Print

print('Simple debug message');

// С переменными
final user = User(name: 'John');
print('User: ${user.name}');

debugPrint

// Автоматически разбивает длинные строки
debugPrint('Very long string that will be split...');

Flutter DevTools logging

import 'package:flutter/foundation.dart';

void main() {
  // Различные уровни логирования
  debugPrint('Info message');
  // Или использовать foundation
}

structured logging

Logger пакет

dependencies:
  logger: ^2.0.0
import 'package:logger/logger.dart';

final logger = Logger();

void main() {
  logger.d('Debug message');
  logger.i('Info message');
  logger.w('Warning message');
  logger.e('Error message');

  // С данными
  logger.i({'user': 'John', 'action': 'login'});

  // Исключения
  try {
    throw Exception('Something went wrong');
  } catch (e) {
    logger.e('Error occurred', e);
  }
}

Custom logger

class AppLogger {
  static const _isDebugMode = kDebugMode;

  static void debug(String message) {
    if (_isDebugMode) {
      print('[DEBUG] $message');
    }
  }

  static void info(String message) {
    if (_isDebugMode) {
      print('[INFO] $message');
    }
  }

  static void warning(String message) {
    print('[WARNING] $message');
  }

  static void error(String message, {Object? error, StackTrace? stackTrace}) {
    print('[ERROR] $message');
    if (error != null) {
      print(error);
    }
    if (stackTrace != null) {
      print(stackTrace);
    }
  }
}

Error handling

Try-catch

Future<void> fetchData() async {
  try {
    final response = await http.get(Uri.parse('https://api.example.com'));
    // Обработка ответа
  } on SocketException {
    print('No internet connection');
  } on HttpException {
    print('HTTP error');
  } catch (e, stackTrace) {
    print('Unexpected error: $e');
    print(stackTrace);
  }
}

Custom exceptions

class AppException implements Exception {
  final String message;
  final int? code;

  AppException(this.message, {this.code});

  @override
  String toString() => 'AppException: $message (code: $code)';
}

// Использование
try {
  // dangerous operation
} on AppException catch (e) {
  print('Handled app exception: $e');
} catch (e) {
  print('Unexpected error: $e');
}

ErrorWidget

class MyErrorWidget extends StatelessWidget {
  final FlutterErrorDetails details;

  const MyErrorWidget(this.details, {super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Icon(Icons.error, size: 48, color: Colors.red),
            SizedBox(height: 16),
            Text('Error occurred'),
            SizedBox(height: 8),
            Text(
              details.exception.toString(),
              style: Theme.of(context).textTheme.bodySmall,
            ),
          ],
        ),
      ),
    );
  }
}

void main() {
  ErrorWidget.builder = (details) {
    return MyErrorWidget(details);
  };

  runApp(MyApp());
}

Crash reporting

Sentry

dependencies:
  sentry_flutter: ^8.0.0
import 'package:sentry_flutter/sentry_flutter.dart';

Future<void> main() async {
  await SentryFlutter.init(
    (options) {
      options.dsn = 'YOUR_SENTRY_DSN';
      options.tracesSampleRate = 1.0;
    },
  );

  runApp(MyApp());
}

// Отправка исключений
try {
  // dangerous operation
} catch (exception, stackTrace) {
  await Sentry.captureException(
    exception,
    stackTrace: stackTrace,
  );
}

// Отправка сообщений
await Sentry.captureMessage('Something happened');

// Breadcrumbs
Sentry.addBreadcrumb(
  Breadcrumb(
    message: 'User clicked button',
    category: 'user',
  ),
);

Firebase Crashlytics

dependencies:
  firebase_crashlytics: ^4.0.0
import 'package:firebase_crashlytics/firebase_crashlytics.dart';

Future<void> main() async {
  await Firebase.initializeApp();

  // Установка Crashlytics
  FlutterError.onError = (errorDetails) {
    FirebaseCrashlytics.instance.recordFlutterFatalError(errorDetails);
  };

  runApp(MyApp());
}

// Запись исключения
try {
  // dangerous operation
} catch (e, stack) {
  await FirebaseCrashlytics.instance.recordError(e, stack);
}

// Пользовательские ключи
await FirebaseCrashlytics.instance.setCustomKey('user_id', '123');

// Пользовательские логи
await FirebaseCrashlytics.instance.log('App started');

Performance profiling

Performance overlay

MaterialApp(
  showPerformanceOverlay: true,
  home: MyApp(),
)

Timeline

import 'package:flutter/foundation.dart';

void main() {
  runApp(MyApp());

  if (kDebugMode) {
    // Профилирование включено
  }
}

Dart DevTools Performance

  1. Запустите приложение в profile режиме
  2. Откройте DevTools
  3. Перейдите в Performance tab
  4. Запишите профиль
  5. Проанализируйте результаты

Network debugging

Dio logging interceptor

dependencies:
  pretty_dio_logger: ^1.3.0
final dio = Dio();

dio.interceptors.add(
  PrettyDioLogger(
    requestHeader: true,
    requestBody: true,
    responseBody: true,
    error: true,
  ),
);

Charles Proxy

Для локального перехвата HTTPS запросов настройте Charles Proxy.

Remote debugging

Flutter Remote Debugging

flutter attach --debug-url=http://localhost:8080

Debugging в production

Remote logging

class RemoteLogger {
  Future<void> log(String message) async {
    await http.post(
      Uri.parse('https://your-api.com/logs'),
      body: {
        'message': message,
        'timestamp': DateTime.now().toIso8601String(),
        'platform': Platform.operatingSystem,
      },
    );
  }
}

Flag для отключения

class Config {
  static const bool enableDebug = kDebugMode;
  static const bool enableRemoteLogging = !kDebugMode;
}

Best Practices

1. Используйте structured logging

logger.i('User logged in', error: null, stackTrace: null);

2. Добавляйте контекст

try {
  // operation
} catch (e, stackTrace) {
  logger.e('Failed to load user data',
    error: e,
    stackTrace: stackTrace,
  );
}

3. Используйте breadcrumbs

Sentry.addBreadcrumb(
  Breadcrumb(message: 'Navigated to home screen'),
);

4. Логируйте важные события

await FirebaseCrashlytics.instance.log('Purchase completed: $orderId');

Заключение

Отладка Flutter приложений в 2025 — это мощный набор инструментов. Используйте DevTools для профилирования и Sentry/Crashlytics для мониторинга production.