mcpField
D
debug:flutter

debug:flutter

github.com/snakeo開発945 回インストール42 週間前に更新
リポジトリ

debug:flutter について

Flutter開発者向けの実用的なデバッグガイドで、レイアウト、状態、プラットフォーム、パフォーマンスの問題を扱います。診断と修正のパターンに加え、Flutter DevToolsの使い方、コマンド、再現・切り分け・修正・検証の手順をまとめています。

主な機能

  1. 01よくあるFlutterエラーへの対処法
  2. 02Flutter DevToolsとインスペクターのガイド
  3. 03エラーの再現と切り分け手順
  4. 04診断・テスト・ビルド用コマンド
  5. 05パフォーマンスとメモリのデバッグ
  6. 06プラットフォーム別のデバッグ情報

活用シーン

  1. 01RenderFlexのオーバーフロー診断
  2. 02ウィジェット破棄後の状態更新の修正
  3. 03プラットフォームチャネルの問題調査
  4. 04ジャンクやメモリリークの追跡

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/snakeo/claude-debug-and-refactor-skills-plugin --skill debug:flutter

インストール後、Claude Code で/debug:flutterを入力して読み込まれたか確認します。

詳細

Flutter デバッグガイド

このスキルでは、よくあるエラーパターン、デバッグツール、問題を効率的に解決するためのベストプラクティスを取り上げ、Flutter アプリケーションを体系的にデバッグする方法を説明します。

よくあるエラーパターン

1. RenderFlex オーバーフローエラー

症状:

  • UI にオーバーフロー領域を示す黄色と黒の縞模様が表示される
  • エラーメッセージ:「A RenderFlex overflowed by X pixels」

原因:

  • Row/Column 内のコンテンツが利用可能な領域を超えている
  • 制約のあるコンテナー内の固定サイズウィジェット
  • 適切なオーバーフロー処理がされていないテキスト

解決策:

// Solution 1: Wrap in SingleChildScrollView
SingleChildScrollView(
  child: Column(
    children: [...],
  ),
)

// Solution 2: Use Flexible or Expanded
Row(
  children: [
    Expanded(
      child: Text('Long text that might overflow...'),
    ),
  ],
)

// Solution 3: Handle text overflow explicitly
Text(
  'Long text...',
  overflow: TextOverflow.ellipsis,
  maxLines: 2,
)
2. Dispose 後に setState() が呼び出された

症状:

  • 実行時エラー:「setState() called after dispose()」
  • 非同期処理の完了後にアプリがクラッシュする

原因:

  • ウィジェットがツリーから削除された後に setState() を呼び出している
  • 画面遷移後に非同期処理が完了する
  • Dispose 済みのウィジェットでタイマーのコールバックが実行される

解決策:

// Solution 1: Check mounted before setState
Future<void> fetchData() async {
  final data = await api.getData();
  if (mounted) {
    setState(() {
      _data = data;
    });
  }
}

// Solution 2: Cancel async operations in dispose
late final StreamSubscription _subscription;

@override
void dispose() {
  _subscription.cancel();
  super.dispose();
}

// Solution 3: Use CancelableOperation
CancelableOperation<Data>? _operation;

Future<void> fetchData() async {
  _operation = CancelableOperation.fromFuture(api.getData());
  final data = await _operation!.value;
  if (mounted) {
    setState(() => _data = data);
  }
}

@override
void dispose() {
  _operation?.cancel();
  super.dispose();
}
3. Null チェック演算子のエラー

症状:

  • エラー:「Null check operator used on a null value」
  • ! を使って null 許容値にアクセスした際にアプリがクラッシュする

原因:

  • null 値に対して ! 演算子を使用している
  • late 変数を初期化していない
  • 非同期コードに競合状態がある

解決策:

// Bad: Using ! without null check
final name = user!.name;

// Good: Use null-aware operators
final name = user?.name ?? 'Unknown';

// Good: Use if-null check
if (user != null) {
  final name = user.name;
}

// Good: Use pattern matching (Dart 3+)
if (user case final u?) {
  final name = u.name;
}

// For late initialization, consider nullable with check
String? _data;

String get data {
  if (_data == null) {
    throw StateError('Data not initialized');
  }
  return _data!;
}
4. プラットフォームチャネルの問題

症状:

  • MissingPluginException
  • ネイティブコードエラーを伴う PlatformException
  • メソッドチャネルが応答しない

解決策:

// Solution 1: Ensure proper platform setup
// Run flutter clean && flutter pub get

// Solution 2: Check method channel registration
const platform = MethodChannel('com.example/channel');

try {
  final result = await platform.invokeMethod('methodName');
} on PlatformException catch (e) {
  debugPrint('Platform error: ${e.message}');
} on MissingPluginException {
  debugPrint('Plugin not registered for this platform');
}

// Solution 3: For plugins, ensure proper initialization
void main() async {
  WidgetsFlutterBinding.ensureInitialized();
  // Initialize plugins here
  runApp(MyApp());
}
5. ビルドコンテキストのエラー

症状:

  • 「Looking up a deactivated widget's ancestor is unsafe」
  • Navigator の操作が失敗する
  • Theme/MediaQuery を利用できない

原因:

  • ウィジェットが破棄された後に context を使用している
  • build の前に initState で context を使用している
  • context の参照を保持している

解決策:

// Bad: Using context in initState
@override
void initState() {
  super.initState();
  // Theme.of(context); // Error!
}

// Good: Use didChangeDependencies
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  final theme = Theme.of(context);
}

// Good: Use Builder for nested contexts
Scaffold(
  body: Builder(
    builder: (context) {
      // This context has access to Scaffold
      return ElevatedButton(
        onPressed: () {
          Scaffold.of(context).showSnackBar(...);
        },
        child: Text('Show Snackbar'),
      );
    },
  ),
)

// Good: Use GlobalKey for cross-widget access
final scaffoldKey = GlobalKey<ScaffoldState>();
6. ホットリロードの失敗

症状:

  • 保存後も変更が反映されない
  • アプリの状態が予期せず失われる
  • 「Hot reload not available」というメッセージが表示される

原因:

  • アプリの初期化コードを変更している
  • const コンストラクターを変更している
  • ネイティブコードを変更している
  • グローバル変数を変更している

解決策:

# Solution 1: Hot restart instead of hot reload
# Press 'R' (capital) in terminal or Shift+Cmd+\ in VS Code

# Solution 2: Full restart
flutter run --no-hot

# Solution 3: Clean build
flutter clean && flutter pub get && flutter run

ホットリスタートが必要なコードパターン:

// Changes to main() require restart
void main() {
  runApp(MyApp()); // Modification here needs restart
}

// Changes to initState logic often need restart
@override
void initState() {
  super.initState();
  _controller = AnimationController(...); // Changes here need restart
}

// Const constructor changes need restart
const MyWidget({super.key}); // Adding/removing const needs restart
7. 垂直ビューポートに無限の高さが指定された

症状:

  • エラー:「Vertical viewport was given unbounded height」
  • Column 内の ListView が失敗する

解決策:

// Bad: ListView in Column without constraints
Column(
  children: [
    ListView(...), // Error!
  ],
)

// Good: Use Expanded
Column(
  children: [
    Expanded(
      child: ListView(...),
    ),
  ],
)

// Good: Use shrinkWrap (for small lists only)
Column(
  children: [
    ListView(
      shrinkWrap: true,
      physics: NeverScrollableScrollPhysics(),
      children: [...],
    ),
  ],
)

// Good: Use SizedBox with fixed height
Column(
  children: [
    SizedBox(
      height: 200,
      child: ListView(...),
    ),
  ],
)
8. RenderBox がレイアウトされていない

症状:

  • エラー:「RenderBox was not laid out」
  • ウィジェットが描画されない

解決策:

// Ensure parent provides constraints
SizedBox(
  width: 200,
  height: 200,
  child: CustomPaint(...),
)

// For intrinsic sizing
IntrinsicHeight(
  child: Row(
    children: [
      Container(color: Colors.red),
      Container(color: Colors.blue),
    ],
  ),
)
9. ParentDataWidget の誤った使用

症状:

  • エラー:「Incorrect use of ParentDataWidget」
  • Positioned/Expanded が誤って使用されている

解決策:

// Bad: Positioned outside Stack
Column(
  children: [
    Positioned(...), // Error!
  ],
)

// Good: Positioned inside Stack
Stack(
  children: [
    Positioned(
      top: 10,
      left: 10,
      child: Text('Hello'),
    ),
  ],
)

// Bad: Expanded outside Flex widget
Container(
  child: Expanded(...), // Error!
)

// Good: Expanded inside Row/Column
Row(
  children: [
    Expanded(child: Text('Hello')),
  ],
)
10. 赤/グレー画面の死

症状:

  • デバッグ/プロファイルモードで赤い画面が表示される
  • リリースモードでグレーの画面が表示される
  • アプリがフリーズしたように見える

原因:

  • 捕捉されていない例外
  • 描画エラー
  • アサーションの失敗

解決策:

// Global error handling
void main() {
  FlutterError.onError = (details) {
    FlutterError.presentError(details);
    // Log to crash reporting service
    crashReporter.recordFlutterError(details);
  };

  PlatformDispatcher.instance.onError = (error, stack) {
    // Handle async errors
    crashReporter.recordError(error, stack);
    return true;
  };

  runApp(MyApp());
}

// Custom error widget
class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    ErrorWidget.builder = (FlutterErrorDetails details) {
      return CustomErrorWidget(details: details);
    };
    return MaterialApp(...);
  }
}

デバッグツール

Flutter DevTools

Flutter のデバッグに利用できる包括的なツールスイートです。

# Launch DevTools
flutter pub global activate devtools
flutter pub global run devtools

# Or access via VS Code Flutter extension

DevTools の主な機能:

  • ウィジェットインスペクター:ウィジェットツリー、プロパティ、描画オブジェクトを調べる
  • パフォーマンスビュー:フレーム描画とジャンクを分析する
  • メモリビュー:割り当てを追跡し、メモリリークを検出する
  • ネットワークビュー:HTTP リクエストを監視する
  • ログビュー:すべてのデバッグ出力を表示する
  • CPU プロファイラー:パフォーマンスのボトルネックを特定する
flutter doctor

環境の問題を診断します。

# Full diagnostic
flutter doctor -v

# Check specific issues
flutter doctor --android-licenses

# Common fixes
flutter clean
flutter pub get
flutter pub upgrade
Dart DevTools デバッガー
// Set breakpoints in code
debugger(when: condition);

// Conditional breakpoints in IDE
// Right-click line number > Add Conditional Breakpoint
ロギングのベストプラクティス
// Basic logging
print('Debug message'); // stdout

// Better: debugPrint for large outputs (prevents dropped logs)
debugPrint('Large debug output...');

// Best: dart:developer log for granular control
import 'dart:developer';

log(
  'User action',
  name: 'UserFlow',
  error: exception,
  stackTrace: stackTrace,
);

// Conditional logging
assert(() {
  debugPrint('Only in debug mode');
  return true;
}());

// Using kDebugMode
import 'package:flutter/foundation.dart';

if (kDebugMode) {
  print('Debug only');
}
Flutter インスペクター(ウィジェットインスペクター)
// Enable debug painting
import 'package:flutter/rendering.dart';

debugPaintSizeEnabled = true;
debugPaintBaselinesEnabled = true;
debugPaintLayerBordersEnabled = true;
debugPaintPointersEnabled = true;

// In widget
debugPrint(context.widget.toStringDeep());

Flutter デバッグの 4 つのフェーズ

フェーズ 1:エラーの種類を特定する

エラーを分類します。

  1. コンパイル時エラー:構文エラー、型エラー(赤い波線)
  2. 実行時エラー:実行中に発生する例外
  3. レイアウトエラー:RenderFlex のオーバーフロー、制約が無限
  4. 状態エラー:Dispose 後の setState、不整合な状態
  5. プラットフォームエラー:ネイティブプラグインの問題、権限
  6. パフォーマンスの問題:ジャンク、メモリリーク、フレームの遅延
# Get detailed error information
flutter analyze
flutter test --reporter expanded
フェーズ 2:再現して切り分ける
// Create minimal reproduction
class DebugWidget extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    // Isolate the problematic widget
    return Container(
      child: ProblematicWidget(),
    );
  }
}

// Use assertions to validate state
assert(data != null, 'Data should not be null at this point');
assert(index >= 0 && index < list.length, 'Index out of bounds: $index');
フェーズ 3:デバッグして修正する
// Add strategic logging
void processData() {
  debugPrint('processData called with: $data');

  try {
    final result = transform(data);
    debugPrint('Transform result: $result');
  } catch (e, stack) {
    debugPrint('Transform failed: $e');
    debugPrint('Stack trace: $stack');
    rethrow;
  }
}

// Use DevTools breakpoints
// Set breakpoints at:
// - Method entry points
// - Before suspected failure points
// - In catch blocks
フェーズ 4:検証と予防
// Add tests for the fix
testWidgets('Widget handles null data gracefully', (tester) async {
  await tester.pumpWidget(
    MaterialApp(
      home: MyWidget(data: null),
    ),
  );

  expect(find.text('No data'), findsOneWidget);
  expect(tester.takeException(), isNull);
});

// Add defensive coding
Widget build(BuildContext context) {
  if (data == null) {
    return const EmptyState();
  }
  return DataDisplay(data: data!);
}

クイックリファレンスコマンド

診断
# Environment check
flutter doctor -v

# Analyze code for issues
flutter analyze

# Check for outdated packages
flutter pub outdated

# Upgrade packages
flutter pub upgrade --major-versions
テスト
# Run all tests
flutter test

# Run specific test file
flutter test test/widget_test.dart

# Run with coverage
flutter test --coverage

# Run integration tests
flutter test integration_test/
ビルドと実行
# Debug mode (default)
flutter run

# Profile mode (for performance debugging)
flutter run --profile

# Release mode
flutter run --release

# Specific device
flutter run -d <device_id>

# List devices
flutter devices
クリーンアップとリセット
# Clean build artifacts
flutter clean

# Get dependencies
flutter pub get

# Reset iOS pods
cd ios && pod deintegrate && pod install && cd ..

# Reset Android
cd android && ./gradlew clean && cd ..
DevTools
# Install DevTools globally
flutter pub global activate devtools

# Run DevTools
flutter pub global run devtools

# Or use dart devtools
dart devtools

パフォーマンスのデバッグ

カクつきの特定
// Enable performance overlay
MaterialApp(
  showPerformanceOverlay: true,
  ...
)

// Or toggle in DevTools
// Performance > Show Performance Overlay
よくあるパフォーマンスの問題
// Bad: Building expensive widgets in build()
@override
Widget build(BuildContext context) {
  final expensiveData = computeExpensiveData(); // Called every rebuild!
  return ExpensiveWidget(data: expensiveData);
}

// Good: Cache expensive computations
late final expensiveData = computeExpensiveData();

// Good: Use const constructors
const MyWidget(key: Key('my-widget'));

// Good: Use RepaintBoundary for isolated repaints
RepaintBoundary(
  child: ExpensiveAnimatedWidget(),
)
メモリのデバッグ
// Check for leaks in DevTools Memory view

// Common leak patterns:
// 1. Streams not disposed
@override
void dispose() {
  _streamSubscription.cancel();
  super.dispose();
}

// 2. Controllers not disposed
@override
void dispose() {
  _textController.dispose();
  _animationController.dispose();
  super.dispose();
}

// 3. Listeners not removed
@override
void dispose() {
  _focusNode.removeListener(_onFocusChange);
  _focusNode.dispose();
  super.dispose();
}

状態管理のデバッグ

Provider/Riverpod
// Debug Provider rebuilds
Consumer<MyModel>(
  builder: (context, model, child) {
    debugPrint('Consumer rebuilding: ${model.value}');
    return Text(model.value);
  },
)

// Use ProviderScope observers
ProviderScope(
  observers: [DebugProviderObserver()],
  child: MyApp(),
)

class DebugProviderObserver extends ProviderObserver {
  @override
  void didUpdateProvider(
    ProviderBase provider,
    Object? previousValue,
    Object? newValue,
    ProviderContainer container,
  ) {
    debugPrint('Provider ${provider.name}: $previousValue -> $newValue');
  }
}
Bloc/Cubit
// Enable Bloc observer
Bloc.observer = DebugBlocObserver();

class DebugBlocObserver extends BlocObserver {
  @override
  void onChange(BlocBase bloc, Change change) {
    super.onChange(bloc, change);
    debugPrint('${bloc.runtimeType} $change');
  }

  @override
  void onError(BlocBase bloc, Object error, StackTrace stackTrace) {
    debugPrint('${bloc.runtimeType} $error $stackTrace');
    super.onError(bloc, error, stackTrace);
  }
}

プラットフォーム固有のデバッグ

Android
# View Android logs
flutter logs

# Or use adb
adb logcat | grep flutter

# Debug native crashes
adb logcat -s AndroidRuntime:E
iOS
# View iOS logs
flutter logs

# Open Xcode console
open ios/Runner.xcworkspace

# Check crash logs
# Xcode > Window > Devices and Simulators > View Device Logs
Web
// Use browser DevTools console
import 'dart:html' as html;

html.window.console.log('Web debug message');

// Check for CORS issues in Network tab
// Check for CSP issues in Console

追加リソース

詳細なドキュメントについては、以下を参照してください。

コメント