SKILL.md
$2a
-
Establish a Performance Baseline
To measure performance programmatically, create an integration test that records a performance timeline.
STOP AND ASK THE USER: "Do you want to run a baseline integration test to capture timeline metrics before optimizing?"
If yes, implement the following exact driver and test implementation:
test_driver/perf_driver.dart (Immutable operation):
import 'package:flutter_driver/flutter_driver.dart' as driver;
import 'package:integration_test/integration_test_driver.dart';
Future<void> main() {
return integrationDriver(
responseDataCallback: (data) async {
if (data != null) {
final timeline = driver.Timeline.fromJson(
data['scrolling_timeline'] as Map<String, dynamic>,
);
final summary = driver.TimelineSummary.summarize(timeline);
await summary.writeTimelineToFile(
'scrolling_timeline',
pretty: true,
includeSummary: true,
);
}
},
);
}
integration_test/scrolling_test.dart:
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:integration_test/integration_test.dart';
import 'package:your_package/main.dart';
void main() {
final binding = IntegrationTestWidgetsFlutterBinding.ensureInitialized();
testWidgets('Performance profiling test', (tester) async {
await tester.pumpWidget(const MyApp());
final listFinder = find.byType(Scrollable);
final itemFinder = find.byKey(const ValueKey('target_item'));
await binding.traceAction(() async {
await tester.scrollUntilVisible(
itemFinder,
500.0,
scrollable: listFinder,
);
}, reportKey: 'scrolling_timeline');
});
}
Run the test using: flutter drive --driver=test_driver/perf_driver.dart --target=integration_test/scrolling_test.dart --profile --no-dds
-
Optimize UI Thread (Build Costs)
If the UI thread exceeds 8ms per frame, refactor the widget tree:
- Localize State: Move
setStatecalls as low in the widget tree as possible.
- **Use
const:** Applyconstconstructors to short-circuit rebuild traversals.
- String Building: Replace
+operators in loops withStringBuffer.
// BAD: Rebuilds entire widget tree
setState(() { _counter++; });
// GOOD: Encapsulated state
class CounterWidget extends StatefulWidget { ... }
// Inside CounterWidget:
setState(() { _counter++; });
// GOOD: Efficient string building
final buffer = StringBuffer();
for (int i = 0; i < 1000; i++) {
buffer.write('Item $i');
}
final result = buffer.toString();
-
Optimize Raster Thread (Rendering Costs)
If the Raster thread exceeds 8ms per frame, eliminate expensive painting operations:
- Replace
Opacitywidgets with semitransparent colors where possible.
- Replace
Opacityin animations withAnimatedOpacityorFadeInImage.
- Avoid
Clip.antiAliasWithSaveLayer. UseborderRadiusproperties on containers instead of explicit clipping widgets.
// BAD: Expensive Opacity widget
Opacity(
opacity: 0.5,
child: Container(color: Colors.red),
)
// GOOD: Semitransparent color
Container(color: Colors.red.withOpacity(0.5))
-
Fix Layout and Intrinsic Passes
Identify and remove excessive layout passes caused by intrinsic operations (e.g., asking all children for their size before laying them out).
- Use lazy builders (
ListView.builder,GridView.builder) for long lists.
- Avoid
ShrinkWrap: trueon scrollables unless absolutely necessary.
-
Handle Framework Breaking Changes (Validate-and-Fix)
Ensure the application complies with recent Flutter optimization changes regarding LayoutBuilder and OverlayEntry. These widgets no longer rebuild implicitly.
- Validate: Check if
LayoutBuilderorOverlayEntryUI fails to update.
- Fix: Wrap the state modification triggering the update in an explicit
setState.
// FIX: Explicit setState for Overlay/Route changes
final newLabel = await Navigator.pushNamed(context, '/bar');
setState(() {
buttonLabel = newLabel;
});
-
Web-Specific Profiling
If profiling for Web, inject timeline events into Chrome DevTools by adding these flags to main() before runApp():
import 'package:flutter/widgets.dart';
import 'package:flutter/rendering.dart';
void main() {
debugProfileBuildsEnabled = true;
debugProfileBuildsEnabledUserWidgets = true;
debugProfileLayoutsEnabled = true;
debugProfilePaintsEnabled = true;
runApp(const MyApp());
}
STOP AND ASK THE USER: "Have you captured the Chrome DevTools performance profile? Please share the timeline event bottlenecks if you need specific refactoring."
Constraints
- NEVER profile performance in Debug mode. Always use
--profilemode on a physical device.
- NEVER override
operator ==onWidgetobjects. It results in O(N²) behavior and degrades performance. Rely onconstcaching instead.
- NEVER put a subtree in an
AnimatedBuilderthat does not depend on the animation. Build the static part once and pass it as thechildparameter.
- DO NOT use constructors with a concrete
Listof children (e.g.,Column,ListView) if most children are off-screen. Always use.builderconstructors for lazy loading.
- DO NOT use
saveLayer()unless absolutely necessary (e.g., dynamic overlapping shapes with transparency). Precalculate and cache static overlapping shapes.