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.

Keep looking

Skills are one crate of 325,949. 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.