flutter-debugging について
Flutter開発者向けの体系的なデバッグスキルです。変更前に根本原因を特定して検証し、問題の再現、正常な実装との比較、仮説の検証、修正確認へと進めます。
主な機能
- 01修正前に根本原因を調査
- 02問題を安定して再現
- 03異常な実装と正常な実装を比較
- 04仮説を一つずつ検証
- 05解析とテストで修正を確認
活用シーン
- 01ウィジェットのビルドや描画エラーを診断
- 02状態、ナビゲーション、ネットワークの不具合を調査
- 03ビルド失敗やテスト失敗を追跡
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ vp-k/ flutter-craft --skill flutter-debuggingインストール後、Claude Code で/flutter-debuggingを入力して読み込まれたか確認します。
詳細
システマティックな Flutter デバッグ
概要
手当たり次第の修正は時間を無駄にし、新たなバグを生みます。応急処置では根本的な問題が隠れてしまいます。
基本原則: 修正を試みる前に、必ず根本原因を特定してください。症状への対処は失敗につながります。
開始時に宣言: 「この問題を調査するため、flutter-debugging スキルを使用します。」
鉄則
NO FIXES WITHOUT ROOT CAUSE INVESTIGATION FIRST
フェーズ 1 を完了していない場合、修正を提案してはいけません。
使用する場面
あらゆる Flutter の技術的な問題で使用します。
- ウィジェットのビルドエラー
- 状態管理のバグ
- テストの失敗
- UI の描画問題
- ナビゲーションの問題
- API/ネットワークエラー
- ビルドの失敗
- プラットフォーム固有の問題
特に次のような場合に使用してください。
- 「ちょっとした応急処置ですぐ直せる」と思える
- すでに複数の修正を試した
- 前回の修正で解決しなかった
- 時間に追われている
Flutter デバッグツール
1. コンソールログ
// Basic logging (truncates long output)
print('Debug: $value');
// No truncation (preferred)
debugPrint('Debug: $value');
// Conditional debug logging
import 'package:flutter/foundation.dart';
if (kDebugMode) {
debugPrint('Only in debug mode: $value');
}
// Developer log with tags
import 'dart:developer';
log('API response', name: 'NetworkService', error: e);
2. Flutter DevTools
# Open DevTools
flutter run --start-paused
# Then press 'd' or use DevTools button in IDE
DevTools のパネル:
- ウィジェットインスペクター: ウィジェットツリー、プロパティ、レイアウト
- パフォーマンス: タイムライン、フレーム描画
- メモリ: ヒープ使用量、リーク
- ネットワーク: HTTP リクエスト/レスポンス
- ログ: すべてのログを一か所に表示
3. デバッグ用ウィジェット
// Show widget boundaries
debugPaintSizeEnabled = true;
// Show baseline alignments
debugPaintBaselinesEnabled = true;
// Show repaint regions
debugRepaintRainbowEnabled = true;
4. ブレークポイント
// Programmatic breakpoint
import 'dart:developer';
debugger();
// Or use IDE breakpoints
4 つのフェーズ
フェーズ 1:根本原因の調査
どんな修正を試みる前にも、必ず次のことを行います。
1. エラーメッセージを注意深く読む
════════════════════════════════════════════════════════════
EXCEPTION CAUGHT BY WIDGETS LIBRARY
════════════════════════════════════════════════════════════
The following assertion was thrown building MyWidget:
'package:flutter/src/widgets/container.dart': Failed assertion: line 287
'child != null || decoration != null || constraints != null'
════════════════════════════════════════════════════════════
エラーを読み飛ばさないでください。 多くの場合、そこには正確な解決策が示されています。
2. 安定して再現する
# Hot reload to reproduce
r # in terminal
# Hot restart (clears state)
R # in terminal
# Full restart
flutter run
- 確実に再現できますか?
- 正確な手順は何ですか?
- ホットリロードとホットリスタートで違いはありますか?
3. 最近の変更を確認する
# What changed recently?
git diff
# Recent commits
git log --oneline -10
# Diff with specific commit
git diff HEAD~3
4. 診断用ログを追加する
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
debugPrint('=== MyWidget.build() ===');
debugPrint('context.mounted: ${context.mounted}');
final state = context.watch<MyState>();
debugPrint('state: $state');
return Container(...);
}
}
フェーズ 2:パターンの分析
1. 動作する例を見つける
# Search for similar working code
grep -r "similar_pattern" lib/
- 問題のあるものと似ていて、動作しているものは何ですか?
- ウィジェットの構造や状態管理を比較します。
2. 参照例と比較する
// Reference: Working widget
class WorkingWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocBuilder<WorkingBloc, WorkingState>(
builder: (context, state) => ...,
);
}
}
// Broken: My widget
class BrokenWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
// What's different?
return BlocBuilder<MyBloc, MyState>(
builder: (context, state) => ...,
);
}
}
3. 違いを特定する
どんなに小さくても、すべての違いを列挙します。
- ウィジェットの種類
- 状態プロバイダーの位置
- BuildContext の使用方法
- ライフサイクルメソッド
フェーズ 3:仮説の立案と検証
1. 仮説を 1 つ立てる
次のように明確に述べます。
「[Y] であるため、問題は [X] だと思います」
例:
「ウィジェットツリー内でこのウィジェットより上に BLoC が提供されていないため、ビルドエラーが発生していると思います」
2. 最小限の変更で検証する
可能な限り最小の変更を加えます。
// Before (broken)
Widget build(BuildContext context) {
final bloc = context.read<MyBloc>();
return ...;
}
// After (testing hypothesis)
Widget build(BuildContext context) {
debugPrint('Looking for MyBloc...');
try {
final bloc = context.read<MyBloc>();
debugPrint('Found: $bloc');
} catch (e) {
debugPrint('Error: $e');
}
return ...;
}
3. 次に進む前に検証する
- 解決しましたか? → フェーズ 4 へ
- 解決しませんでしたか? → 新しい仮説を立てる
- さらに修正を重ねないでください
フェーズ 4:実装
1. テストケースを作成する(優先度に応じて任意)
Repository/DataSource の問題(優先度 1)の場合:
test('should return user when API succeeds', () async {
// Arrange
when(mockApi.getUser(any)).thenAnswer((_) async => userModel);
// Act
final result = await repository.getUser('123');
// Assert
expect(result, equals(userEntity));
});
2. 修正を 1 つ実装する
一度に変更するのは 1 つだけです。
// Fix the root cause identified in Phase 3
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
return BlocProvider(
create: (_) => getIt<MyBloc>(),
child: BlocBuilder<MyBloc, MyState>(...),
);
}
}
3. 修正を検証する
# Run analysis
flutter analyze
# Run tests
flutter test
# Manual verification
flutter run
# Test the specific scenario
4. 修正で解決しない場合
- 試した修正の数を数えます。
- 3 未満の場合:フェーズ 1 に戻り、再分析する
- 3 以上の場合: 作業を止めて、アーキテクチャを見直す
Flutter でよくある問題と根本原因
| 症状 | よくある根本原因 |
|---|---|
| "No ancestor found" | Provider/BLoC がウィジェットツリー内で対象より上にない |
| "setState after dispose" | ウィジェットの破棄後に非同期処理が完了している |
| "RenderBox not laid out" | 制約が無制限になっている(Column の中に Column があるなど) |
| "Null check operator" | 変数が初期化されていない、または API が null を返した |
| "Build during build" | ビルドフェーズ中に setState が呼び出されている |
| "Ticker not disposed" | with TickerProviderStateMixin または dispose がない |
危険信号 — 作業を止めて手順に従う
次のように考えていることに気づいたら:
- 「とりあえず応急処置しよう」
- 「try-catch で囲めばいい」
- 「ここに null チェックを追加しよう」
- 「Container で囲めばいい」
- 「とりあえずホットリスタートしよう」
- 「たぶん状態の問題だから、リセットしてみよう」
これらはすべて、作業を止めてフェーズ 1 に戻るべきサインです。
必須のサブスキル
バグを修正した後、必ず次を呼び出してください。 → flutter-craft:flutter-verification
flutter analyze、flutter test、手動テストで修正を検証してください。
クイックリファレンス
| フェーズ | Flutter での作業 | 成功基準 |
|---|---|---|
| 1. 根本原因 | エラーを読み、再現し、debugPrint を追加する | 何が起きているのか、なぜ起きているのかを理解する |
| 2. パターン | 動作するウィジェットを見つけ、比較する | 違いを特定する |
| 3. 仮説 | 仮説を立て、最小限のテストを行う | 仮説が確認されるか、新しい仮説が得られる |
| 4. 実装 | 優先度に基づいてテストを作成し、修正して、検証する | バグが解消され、解析に問題がない |
系統的に進める理由
場当たり的な修正をすると、試行錯誤を繰り返すことになりがちです。失敗した推測のたびにコードが汚れ、元の症状が見えにくくなります。根本原因を最初に特定するデバッグなら、変更を最小限に抑え、修正を検証可能にし、「いろいろ試す」うちに新たなバグを持ち込むのを防げます。