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. 01诊断 RenderFlex 溢出
  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()
  • 导航离开后异步操作才完成
  • 已销毁组件上的计时器回调

解决方案:

// 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 check operator used on a null value”
  • 使用 ! 访问可空值时应用崩溃

原因:

  • 对空值使用 ! 运算符
  • 未初始化 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
  • 在构建之前于 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 主要功能:

  • Widget Inspector:检查组件树、属性和渲染对象
  • Performance View:分析帧渲染和卡顿
  • Memory View:跟踪内存分配并检测内存泄漏
  • Network View:监控 HTTP 请求
  • Logging View:查看所有调试输出
  • CPU Profiler:识别性能瓶颈
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 Inspector(Widget Inspector)
// Enable debug painting
import 'package:flutter/rendering.dart';

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

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

Flutter 调试的四个阶段

阶段 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

更多资源

详细文档:

评论

支持 Markdown