Flutter performance
When to activate: Flutter performance, const constructors, RepaintBoundary, ListView.builder, image caching, DevTools, jank, build tracing, profilingFrom its SKILL.md
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill flutter-performanceAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
One thing to look at
- 0 stars0 stars. Stars are a popularity signal and not a quality one, but at this level it is likely that nobody has read this closely except its author, and you would be relying on your own review.
SKILL.md
5.7 KB, ~1.3k tokens by cl100k_base, as published. Nobody here has run it
Flutter Performance Patterns
const Constructors
The single highest-ROI optimization. Flutter skips rebuilding const widgets.
// 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
// 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.
// 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
// 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
// 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
// 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
# 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
// 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
// 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, notColumn+map - Heavy/animated widgets wrapped in
RepaintBoundary - Images decoded at display size (
cacheWidth/cacheHeight) - No synchronous heavy work in
build()orinitState() - Profiled in
--profilemode, not debug - Frame times consistently under 16ms
What ships with it
Read from the repository
Just SKILL.md. No reference files, no scripts.
Gives 0 of the 12 instructions most performance cost skills give in ~1.3k tokens
Counted across 803 of the 1,058 authors here whose files we hold, read 2026-08-07
- Keep skill files under 500 lines or tokensin 82 of 803, across 16 files
- Use imperative form in instructionsin 80 of 803, across 9 files
- Draft assertions while test runs are in progressin 75 of 803, across 9 files
- Create two to three realistic test promptsin 74 of 803, across 9 files
- Write skill descriptions to be pushyin 72 of 803, across 7 files
- Save test cases to evals JSONin 72 of 803, across 6 files
- Ask questions about edge cases and input formatsin 72 of 803, across 7 files
- Save timing data immediately when runs completein 70 of 803, across 5 files
- Include all trigger conditions in the skill descriptionin 69 of 803, across 3 files
- Launch all test runs in a single turn or simultaneouslyin 69 of 803, across 3 files
- Capture intent before writing a skillin 67 of 803, across 1 file
- Import directly instead of barrel filesin 52 of 803, across 15 files
Said here and by no other author read
- declare static widgets as const
- wrap heavy animated widgets in RepaintBoundary
- extract inline callbacks to named widgets
- narrow state rebuilds using select or buildWhen
- specify cache dimensions for network images
- precache important images
Grouped from the skills themselves: near-identical wordings counted once, and counted by distinct author, so one author publishing three of these counts once. Length counted with cl100k_base; the agent that loads this file may tokenize it differently.