# Flutter Performance

> When to activate: Flutter performance, const constructors, RepaintBoundary, ListView.builder, image caching, DevTools, jank, build tracing, profiling

- Skill: `mattakushi432/flutter-performance` (Agent Skill)
- Install (CLI): `npx skillmds@latest add mattakushi432/flutter-performance`
- Raw SKILL.md: https://api.skillmd.com/api/skills/mattakushi432/flutter-performance/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Coding & Dev Tools
- Author: Mattakushi432 (https://skillmd.com/u/mattakushi432)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/mattakushi432/flutter-performance

---

# Flutter Performance Patterns

## const Constructors

The single highest-ROI optimization. Flutter skips rebuilding const widgets.

```dart
// BAD: new instance every build
Text('Hello');
EdgeInsets.all(16);
SizedBox(width: 8);

// GOOD: shared, never rebuilt
const Text('Hello');
const EdgeInsets.all(16);
const SizedBox(width: 8);

// Widget must declare const constructor
class MyIcon extends StatelessWidget {
  const MyIcon({super.key}); // required for const at call site
}
```

## Efficient List Rendering

```dart
// BAD: builds all items at once
Column(children: items.map((i) => ItemWidget(item: i)).toList());

// GOOD: lazy, only builds visible items
ListView.builder(
  itemCount: items.length,
  itemBuilder: (context, i) => ItemWidget(item: items[i]),
);

// For variable height items with separators
ListView.separated(
  itemCount: items.length,
  separatorBuilder: (_, __) => const Divider(),
  itemBuilder: (context, i) => ItemWidget(item: items[i]),
);

// Sliver for mixed content in single scroll
CustomScrollView(slivers: [
  const SliverAppBar(pinned: true, title: Text('Title')),
  SliverList.builder(
    itemCount: items.length,
    itemBuilder: (context, i) => ItemWidget(item: items[i]),
  ),
]);
```

## RepaintBoundary

Isolates subtrees from parent repaints. Use around frequently animating widgets.

```dart
// Wrap independently animating widget
RepaintBoundary(
  child: AnimatedWidget(), // repaints without repainting siblings
);

// Wrap heavy static widget to avoid repaint propagation
RepaintBoundary(
  child: ComplexStaticChart(),
);
```

## Avoiding Unnecessary Rebuilds

```dart
// BAD: callback inline creates new closure every build
ListView.builder(
  itemBuilder: (context, i) => GestureDetector(
    onTap: () => print(i), // new closure each rebuild
    child: ItemWidget(item: items[i]),
  ),
);

// GOOD: extract to method or widget
class ItemRow extends StatelessWidget {
  const ItemRow({super.key, required this.item, required this.onTap});
  final Item item;
  final VoidCallback onTap;
  // Only rebuilds when item or onTap changes
}

// Use select to narrow rebuilds in Riverpod
final userName = ref.watch(userProvider.select((u) => u.name));
// Only rebuilds when name changes, not when other user fields change

// BlocBuilder buildWhen
BlocBuilder<UserBloc, UserState>(
  buildWhen: (prev, curr) => prev.name != curr.name,
  builder: (context, state) => Text(state.name),
);
```

## Image Optimization

```dart
// Specify cacheWidth/cacheHeight to decode at display size
Image.network(
  url,
  cacheWidth: 200,  // decode at 200px, not original resolution
  cacheHeight: 200,
);

// Use cached_network_image for persistent disk cache
CachedNetworkImage(
  imageUrl: url,
  placeholder: (context, url) => const CircularProgressIndicator(),
  errorWidget: (context, url, error) => const Icon(Icons.error),
  memCacheWidth: 200,
);

// Precache important images
@override
void didChangeDependencies() {
  super.didChangeDependencies();
  precacheImage(const AssetImage('assets/hero.png'), context);
}
```

## Reducing Widget Build Cost

```dart
// Extract static parts to fields/getters (built once, not every build)
class _MyWidgetState extends State<MyWidget> {
  // Defined once
  static const _divider = Divider(color: Colors.grey);
  static const _spacing = SizedBox(height: 16);

  @override
  Widget build(BuildContext context) => Column(children: [
    _spacing,
    const Text('Header'), // const = shared instance
    _divider,
  ]);
}
```

## DevTools Performance Profiling

```bash
# Run in profile mode (not debug — debug is slow)
flutter run --profile

# Record timeline
flutter pub global activate devtools
flutter pub global run devtools

# Command line timeline dump
flutter drive --profile --trace-startup --target=test_driver/app.dart
```

Key DevTools panels:
- **Performance**: frame timeline, identify jank (>16ms frames)
- **CPU Profiler**: find hot methods
- **Memory**: heap snapshot, detect leaks
- **Widget Inspector**: visualize rebuild counts

## Startup Performance

```dart
// Defer initialization
void main() {
  runApp(const MyApp()); // show UI immediately

  // Heavy init after first frame
  WidgetsBinding.instance.addPostFrameCallback((_) async {
    await HeavyService.initialize();
    await PushNotifications.initialize();
  });
}

// Minimal first frame — avoid heavy work in initState
@override
void initState() {
  super.initState();
  // BAD: blocks first build
  // _data = heavySync(); 
  // GOOD: async after frame
  Future.microtask(() async => setState(() => _data = await fetchData()));
}
```

## Compute for Isolate Offloading

```dart
// Move CPU-heavy work off the UI thread
import 'package:flutter/foundation.dart';

Future<List<Product>> parseProducts(String json) async {
  return compute(_parseProductsIsolate, json);
}

List<Product> _parseProductsIsolate(String json) {
  // runs in separate isolate, safe for heavy parsing
  final data = jsonDecode(json) as List;
  return data.map((e) => Product.fromJson(e as Map<String, dynamic>)).toList();
}
```

## Checklist

- [ ] All static widgets use `const`
- [ ] Lists use `ListView.builder`, not `Column` + `map`
- [ ] Heavy/animated widgets wrapped in `RepaintBoundary`
- [ ] Images decoded at display size (`cacheWidth`/`cacheHeight`)
- [ ] No synchronous heavy work in `build()` or `initState()`
- [ ] Profiled in `--profile` mode, not debug
- [ ] Frame times consistently under 16ms

