Claude
Skills
Sign in
Back

debug:flutter

Included with Lifetime
$97 forever

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.

Code Review

What this skill does


# 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:**
```dart
// 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:**
```dart
// 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:**
```dart
// 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:**
```dart
// 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:**
```dart
// 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:**
```bash
# 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:**
```dart
// 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:**
```dart
// 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:**
```dart
// 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:**
```dart
// 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:**
```dart
// 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 = (Flu

Related in Code Review