Claude-skill-registry flutter-performance-docs
[Flutter] Flutter performance best practices and optimization guide. Build cost, rendering, lists, animations, and anti-patterns. (project)
install
source · Clone the upstream repo
git clone https://github.com/majiayu000/claude-skill-registry
Claude Code · Install into ~/.claude/skills/
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/flutter-performance-docs" ~/.claude/skills/majiayu000-claude-skill-registry-flutter-performance-docs && rm -rf "$T"
manifest:
skills/data/flutter-performance-docs/SKILL.mdsource content
Flutter Performance Best Practices
The 16ms Rule
| Display | Frame Budget | Build | Render |
|---|---|---|---|
| 60Hz | 16ms | ~8ms | ~8ms |
| 120Hz | 8ms | ~4ms | ~4ms |
Always profile in profile mode, not debug mode.
1. Control build() Cost
Split Large Widgets
// BAD: Monolithic widget class MyPage extends StatelessWidget { Widget build(context) => Column(children: [header, content, footer]); } // GOOD: Split into smaller widgets class MyPage extends StatelessWidget { Widget build(context) => Column(children: [ HeaderWidget(), ContentWidget(), FooterWidget(), ]); }
Localize setState()
// BAD: setState high in tree class Parent extends StatefulWidget { void update() => setState(() {}); // Rebuilds entire subtree } // GOOD: setState only where needed class Child extends StatefulWidget { void update() => setState(() {}); // Only rebuilds this widget }
Use const Constructors
// GOOD: Flutter skips rebuild for const widgets const Text('Hello'); const SizedBox(height: 16); const MyCustomWidget();
Prefer StatelessWidget Over Functions
// BAD: Function returns widget Widget buildHeader() => Container(...); // GOOD: StatelessWidget (enables const, better rebuild tracking) class Header extends StatelessWidget { const Header(); Widget build(context) => Container(...); }
2. Lists & Grids
Use Lazy Builders
// BAD: Builds all items at once ListView(children: items.map((i) => ItemWidget(i)).toList()) // GOOD: Only builds visible items ListView.builder( itemCount: items.length, itemBuilder: (context, index) => ItemWidget(items[index]), )
Avoid Intrinsic Passes
Intrinsic passes poll all cells for sizing - expensive for large grids.
// BAD: Causes intrinsic pass IntrinsicHeight(child: Row(children: [...])) // GOOD: Fixed sizes SizedBox(height: 100, child: Row(children: [...]))
Debug: Enable "Track layouts" in DevTools to see intrinsic timeline events.
3. Minimize saveLayer()
saveLayer() allocates offscreen buffer - expensive!
Widgets That Trigger saveLayer()
ShaderMaskColorFilter
(ifChip
)disabledColorAlpha != 0xff
(withText
)overflowShader
Debug
Enable
PerformanceOverlayLayer.checkerboardOffscreenLayers in DevTools.
4. Opacity & Clipping
Opacity
// BAD: Wraps widget in Opacity Opacity(opacity: 0.5, child: Image(...)) // GOOD: Apply directly to image Image(..., color: Colors.white.withOpacity(0.5), colorBlendMode: BlendMode.modulate) // GOOD: For text, use semitransparent color Text('Hello', style: TextStyle(color: Colors.black54)) // GOOD: For animations AnimatedOpacity(opacity: _visible ? 1.0 : 0.0, child: ...) FadeInImage(placeholder: ..., image: ...)
Clipping
// BAD: Explicit clipping ClipRRect(borderRadius: BorderRadius.circular(8), child: Container(...)) // GOOD: Use decoration borderRadius Container( decoration: BoxDecoration(borderRadius: BorderRadius.circular(8)), child: ... )
Default is
Clip.none - enable clipping only when needed.
5. Animations
TransitionBuilder Pattern
// BAD: Rebuilds everything AnimatedBuilder( animation: _controller, builder: (context, child) => Transform.rotate( angle: _controller.value, child: ExpensiveWidget(), // Rebuilt every frame! ), ) // GOOD: Child is not rebuilt AnimatedBuilder( animation: _controller, child: ExpensiveWidget(), // Built once builder: (context, child) => Transform.rotate( angle: _controller.value, child: child, // Reused ), )
Pre-clip Images for Animation
// BAD: Clips during animation AnimatedBuilder( builder: (_, __) => ClipRRect( borderRadius: BorderRadius.circular(8), child: Image(...), ), ) // GOOD: Pre-clipped image final clippedImage = ClipRRect( borderRadius: BorderRadius.circular(8), child: Image(...), ); AnimatedBuilder( child: clippedImage, builder: (_, child) => Transform.scale(scale: _scale, child: child), )
6. String Concatenation
// BAD: Creates intermediate strings String result = ''; for (var item in items) { result += item.toString(); // O(n²) } // GOOD: Single concatenation final buffer = StringBuffer(); for (var item in items) { buffer.write(item.toString()); } final result = buffer.toString(); // O(n)
Anti-Patterns Summary
| Anti-Pattern | Solution |
|---|---|
widget in animations | , |
| |
in animations | Pre-clip before animating |
Override on Widget | Only for leaf widgets with efficient comparison |
| Large monolithic widgets | Split into smaller widgets |
high in tree | Localize to affected subtree |
| Fixed sizes or custom |
Debugging Tools
| Tool | Purpose |
|---|---|
| DevTools Performance View | Timeline, frame analysis |
| DevTools Inspector | Track widget rebuilds |
| "Track layouts" option | Find intrinsic passes |
| Find saveLayer calls |
| Profile mode build | Accurate performance measurement |
Mobile: Use Impeller
Impeller is Flutter's default graphics renderer. Eliminates shader compilation jank.
# Verify Impeller is enabled (default on iOS/Android) flutter run --enable-impeller