agentsclimarketplace

Flutter performance

Skill Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack/plugins/devtools-pack/skills/flutter-performance

When to activate: Flutter performance, const constructors, RepaintBoundary, ListView.builder, image caching, DevTools, jank, build tracing, profilingFrom its SKILL.md

Install
npx -y skills add Mattakushi432/Claude-Code-Skills-Custom-DevTools-Pack --skill flutter-performance

Assembled 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, 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

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.

Keep looking

Skills are one crate of 326,852. Ordering is by how many stacks a row turns up in, so the top of any crate is what has actually been picked rather than what has the most stars.