
debug:flutter
Debug Flutter applications systematically with this comprehensive troubleshooting skill. Covers RenderFlex overflow errors, setState() after dispose() issues, null check operator failures, platform channel problems, build context errors, and hot reload failures. Provides structured four-phase debugging methodology with Flutter DevTools, widget inspector, performance profiling, and platform-specific debugging for Android, iOS, and web targets.
Debug Flutter applications systematically with this comprehensive troubleshooting skill. Covers RenderFlex overflow errors, setState() after dispose() issues, null check operator failures, platform channel problems, build context errors, and hot reload failures. Provides structured four-phase debugging methodology with Flutter DevTools, widget inspector, performance profiling, and platform-specific debugging for Android, iOS, and web targets.
Flutter Debugging Guide
This skill provides a systematic approach to debugging Flutter applications, covering common error patterns, debugging tools, and best practices for efficient problem resolution.
Common Error Patterns
1. RenderFlex Overflow Error
Symptoms:
- Yellow and black stripes appear in the UI indicating overflow area
- Error message: "A RenderFlex overflowed by X pixels"
Causes:
- Content exceeds available space in Row/Column
- Fixed-size widgets in constrained containers
- Text without proper overflow handling
Solutions:
// 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. setState() Called After Dispose
Symptoms:
- Runtime error: "setState() called after dispose()"
- App crashes after async operations complete
Causes:
- Calling setState() after widget is removed from tree
- Async operations completing after navigation away
- Timer callbacks on disposed widgets
Solutions:
// 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 Errors
Symptoms:
- Error: "Null check operator used on a null value"
- App crashes when accessing nullable values with
!
Causes:
- Using
!operator on null values - Not initializing late variables
- Race conditions in async code
Solutions:
// 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. Platform Channel Issues
Symptoms:
- MissingPluginException
- PlatformException with native code errors
- Method channel not responding
Solutions:
// 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. Build Context Errors
Symptoms:
- "Looking up a deactivated widget's ancestor is unsafe"
- Navigator operations fail
- Theme/MediaQuery not available
Causes:
- Using context after widget disposal
- Using context in initState before build
- Storing context references
Solutions:
// 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 Failures
Symptoms:
- Changes not reflecting after save
- App state lost unexpectedly
- "Hot reload not available" message
Causes:
- Changing app initialization code
- Modifying const constructors
- Native code changes
- Global variable mutations
Solutions:
# 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
Code patterns that require hot restart:
// 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 Given Unbounded Height
Symptoms:
- Error: "Vertical viewport was given unbounded height"
- ListView inside Column fails
Solutions:
// 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 Was Not Laid Out
Symptoms:
- Error: "RenderBox was not laid out"
- Widget fails to render
Solutions:
// 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. Incorrect Use of ParentDataWidget
Symptoms:
- Error: "Incorrect use of ParentDataWidget"
- Positioned/Expanded used incorrectly
Solutions:
// 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. Red/Grey Screen of Death
Symptoms:
- Red screen in debug/profile mode
- Grey screen in release mode
- App appears frozen
Causes:
- Uncaught exceptions
- Rendering errors
- Failed assertions
Solutions:
// 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(...);
}
}
Debugging Tools
Flutter DevTools
The comprehensive suite for Flutter debugging:
# Launch DevTools
flutter pub global activate devtools
flutter pub global run devtools
# Or access via VS Code Flutter extension
Key DevTools Features:
- Widget Inspector: Examine widget tree, properties, and render objects
- Performance View: Analyze frame rendering and jank
- Memory View: Track allocations and detect memory leaks
- Network View: Monitor HTTP requests
- Logging View: View all debug output
- CPU Profiler: Identify performance bottlenecks
flutter doctor
Diagnose environment issues:
# Full diagnostic
flutter doctor -v
# Check specific issues
flutter doctor --android-licenses
# Common fixes
flutter clean
flutter pub get
flutter pub upgrade
Dart DevTools Debugger
// Set breakpoints in code
debugger(when: condition);
// Conditional breakpoints in IDE
// Right-click line number > Add Conditional Breakpoint
Logging Best Practices
// 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());
The Four Phases of Flutter Debugging
Phase 1: Identify the Error Type
Categorize the error:
- Compile-time errors: Syntax, type errors (red squiggles)
- Runtime errors: Exceptions during execution
- Layout errors: RenderFlex overflow, unbounded constraints
- State errors: setState after dispose, inconsistent state
- Platform errors: Native plugin issues, permissions
- Performance issues: Jank, memory leaks, slow frames
# Get detailed error information
flutter analyze
flutter test --reporter expanded
Phase 2: Reproduce and Isolate
// 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');
Phase 3: Debug and Fix
// 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
Phase 4: Verify and Prevent
// 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!);
}
Quick Reference Commands
Diagnostics
# 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
Testing
# 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/
Build and Run
# 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 and Reset
# 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
Performance Debugging
Identify Jank
// Enable performance overlay
MaterialApp(
showPerformanceOverlay: true,
...
)
// Or toggle in DevTools
// Performance > Show Performance Overlay
Common Performance Issues
// 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(),
)
Memory Debugging
// 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();
}
State Management Debugging
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);
}
}
Platform-Specific Debugging
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
Additional Resources
For detailed documentation:
You Might Also Like
Related Skills

fix
Use when you have lint errors, formatting issues, or before committing code to ensure it passes CI.
facebook
frontend-testing
Generate Vitest + React Testing Library tests for Dify frontend components, hooks, and utilities. Triggers on testing, spec files, coverage, Vitest, RTL, unit tests, integration tests, or write/review test requests.
langgenius
frontend-code-review
Trigger when the user requests a review of frontend files (e.g., `.tsx`, `.ts`, `.js`). Support both pending-change reviews and focused file reviews while applying the checklist rules.
langgenius
code-reviewer
Use this skill to review code. It supports both local changes (staged or working tree) and remote Pull Requests (by ID or URL). It focuses on correctness, maintainability, and adherence to project standards.
google-gemini
session-logs
Search and analyze your own session logs (older/parent conversations) using jq.
moltbot
