performance-optimization
12
总安装量
7
周安装量
#26295
全站排名
安装命令
npx skills add https://github.com/kaakati/rails-enterprise-dev --skill performance-optimization
Agent 安装分布
antigravity
5
claude-code
5
opencode
5
codex
5
windsurf
4
Skill 文档
Performance Optimization for Flutter
Complete guide to building high-performance Flutter applications that maintain 60 FPS (or 120 FPS on capable devices).
Performance Goals
- 60 FPS: Each frame must render in < 16ms
- 120 FPS: Each frame must render in < 8ms (for high refresh rate displays)
- Jank-free: No dropped frames during scrolling or animations
- Fast startup: App ready in < 2 seconds
- Low memory: < 100MB for typical screens
Widget Optimization
Use const Constructors
Const widgets are built once and reused:
// â BAD - Widget rebuilt every time
Widget build(BuildContext context) {
return Container(
padding: EdgeInsets.all(16.0),
child: Text('Static Text'),
);
}
// â
GOOD - Widget built once, reused
Widget build(BuildContext context) {
return const Padding(
padding: EdgeInsets.all(16.0),
child: Text('Static Text'),
);
}
// â
GOOD - Individual widgets const
Widget build(BuildContext context) {
return Column(
children: const [
Icon(Icons.home),
SizedBox(height: 8),
Text('Home'),
],
);
}
Rule: If a widget’s properties don’t change, make it const.
Minimize Widget Rebuilds
Use Obx strategically to limit rebuild scope:
// â BAD - Entire Column rebuilds
Obx(() => Column(
children: [
Text(controller.title.value),
ExpensiveWidget(),
AnotherExpensiveWidget(),
],
))
// â
GOOD - Only Text rebuilds
Column(
children: [
Obx(() => Text(controller.title.value)),
const ExpensiveWidget(),
const AnotherExpensiveWidget(),
],
)
Proper Key Usage
Keys help Flutter identify which widgets to reuse:
// â BAD - No keys, Flutter may rebuild unnecessarily
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(title: Text(items[index].name));
},
)
// â
GOOD - ValueKey helps Flutter track items
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(items[index].id),
title: Text(items[index].name),
);
},
)
// When to use keys:
// - ObjectKey: Compare entire object
// - ValueKey: Compare single value (id, index)
// - UniqueKey: Force rebuild
// - GlobalKey: Access widget state from anywhere (expensive, use sparingly)
Extract Widgets
Extract complex widgets to reduce rebuild scope:
// â BAD - All items rebuild when list changes
class MyList extends StatelessWidget {
Widget build(BuildContext context) {
return Obx(() => ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
final item = controller.items[index];
return Container(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(item.title),
Text(item.subtitle),
Row(
children: [
Icon(Icons.star),
Text('${item.rating}'),
],
),
],
),
);
},
));
}
}
// â
GOOD - Extract item widget
class MyList extends StatelessWidget {
Widget build(BuildContext context) {
return Obx(() => ListView.builder(
itemCount: controller.items.length,
itemBuilder: (context, index) {
return ItemWidget(item: controller.items[index]);
},
));
}
}
class ItemWidget extends StatelessWidget {
final Item item;
const ItemWidget({Key? key, required this.item}) : super(key: key);
Widget build(BuildContext context) {
return Container(
padding: const EdgeInsets.all(16),
child: Column(
children: [
Text(item.title),
Text(item.subtitle),
Row(
children: [
const Icon(Icons.star),
Text('${item.rating}'),
],
),
],
),
);
}
}
List Performance
Use ListView.builder
Never use ListView(children: [...]) for large lists:
// â BAD - All items created upfront
ListView(
children: items.map((item) => ItemWidget(item: item)).toList(),
)
// â
GOOD - Items created on demand
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
)
// â
GOOD - Separated items (for sticky headers)
ListView.separated(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
separatorBuilder: (context, index) => const Divider(),
)
Optimize List Scroll Performance
// Add cacheExtent for smoother scrolling
ListView.builder(
cacheExtent: 500, // Render items 500px off-screen
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
)
// Use addAutomaticKeepAlives: false if items don't need to preserve state
ListView.builder(
addAutomaticKeepAlives: false,
addRepaintBoundaries: true,
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(item: items[index]),
)
Infinite Scroll / Pagination
class ProductListController extends GetxController {
final _items = <Product>[].obs;
List<Product> get items => _items;
final _isLoading = false.obs;
bool get isLoading => _isLoading.value;
int _currentPage = 1;
bool _hasMore = true;
final ScrollController scrollController = ScrollController();
void onInit() {
super.onInit();
loadItems();
scrollController.addListener(_onScroll);
}
void onClose() {
scrollController.dispose();
super.onClose();
}
void _onScroll() {
if (scrollController.position.pixels >=
scrollController.position.maxScrollExtent - 200) {
if (!_isLoading.value && _hasMore) {
loadMore();
}
}
}
Future<void> loadItems() async {
_isLoading.value = true;
final result = await repository.getProducts(page: 1);
result.fold(
(failure) => {},
(products) {
_items.value = products;
_hasMore = products.length >= 20; // Assuming page size of 20
},
);
_isLoading.value = false;
}
Future<void> loadMore() async {
_isLoading.value = true;
_currentPage++;
final result = await repository.getProducts(page: _currentPage);
result.fold(
(failure) => _currentPage--,
(products) {
_items.addAll(products);
_hasMore = products.length >= 20;
},
);
_isLoading.value = false;
}
}
Image Optimization
Use cached_network_image
import 'package:cached_network_image/cached_network_image.dart';
// â BAD - No caching, re-downloads every time
Image.network('https://example.com/image.jpg')
// â
GOOD - Cached, with placeholders
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => const CircularProgressIndicator(),
errorWidget: (context, url, error) => const Icon(Icons.error),
fadeInDuration: const Duration(milliseconds: 300),
memCacheWidth: 400, // Resize for memory efficiency
)
Optimize Image Sizes
// Specify dimensions to avoid unnecessary rendering
CachedNetworkImage(
imageUrl: product.imageUrl,
width: 200,
height: 200,
fit: BoxFit.cover,
memCacheWidth: 200 * 2, // 2x for high DPI displays
memCacheHeight: 200 * 2,
)
// For list thumbnails, use lower resolution
CachedNetworkImage(
imageUrl: product.thumbnailUrl, // Server-side thumbnail
width: 50,
height: 50,
memCacheWidth: 100,
memCacheHeight: 100,
)
Precache Images
void didChangeDependencies() {
super.didChangeDependencies();
// Precache images that will be needed soon
precacheImage(
CachedNetworkImageProvider(product.imageUrl),
context,
);
}
Memory Management
Dispose Controllers and Listeners
class MyController extends GetxController {
late final StreamSubscription _subscription;
late final ScrollController scrollController;
late final TextEditingController textController;
void onInit() {
super.onInit();
scrollController = ScrollController();
textController = TextEditingController();
_subscription = someStream.listen((data) {
// Handle data
});
}
void onClose() {
// CRITICAL: Dispose all resources
scrollController.dispose();
textController.dispose();
_subscription.cancel();
super.onClose();
}
}
Avoid Memory Leaks with GetX
// â BAD - Permanent controller never disposed
Get.put(MyController(), permanent: true);
// â
GOOD - Controller disposed when not needed
Get.lazyPut(() => MyController());
// â
GOOD - Explicitly control lifecycle
Get.put(MyController(), tag: 'unique-tag');
// Later: Get.delete<MyController>(tag: 'unique-tag');
Use WeakReference for Callbacks
class MyController extends GetxController {
Timer? _timer;
void onInit() {
super.onInit();
// Use WeakReference to avoid keeping controller alive
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
if (!isClosed) {
updateData();
}
});
}
void onClose() {
_timer?.cancel();
super.onClose();
}
}
Lazy Loading and Code Splitting
Deferred Imports
// feature_a.dart - Large feature module
import 'package:flutter/material.dart';
class FeatureAPage extends StatelessWidget {
// Heavy feature implementation
}
// main.dart - Lazy load feature
import 'feature_a.dart' deferred as feature_a;
void navigateToFeatureA() async {
await feature_a.loadLibrary(); // Load code on demand
Get.to(() => feature_a.FeatureAPage());
}
Lazy Controller Initialization
// â BAD - All controllers loaded at startup
void main() {
Get.put(HomeController());
Get.put(ProfileController());
Get.put(SettingsController());
runApp(MyApp());
}
// â
GOOD - Controllers loaded when needed
class HomeBinding extends Bindings {
void dependencies() {
Get.lazyPut(() => HomeController());
}
}
GetPage(
name: '/home',
page: () => HomePage(),
binding: HomeBinding(), // Loaded only when route accessed
)
Animation Performance
Use AnimatedWidget
// â BAD - Rebuilds entire widget tree
class MyWidget extends StatefulWidget {
State<MyWidget> createState() => _MyWidgetState();
}
class _MyWidgetState extends State<MyWidget>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
Widget build(BuildContext context) {
return Transform.scale(
scale: _controller.value,
child: ExpensiveWidget(), // Rebuilt every frame!
);
}
}
// â
GOOD - Only animated part rebuilds
class ScaleTransition extends AnimatedWidget {
const ScaleTransition({
required Animation<double> scale,
required this.child,
}) : super(listenable: scale);
final Widget child;
Widget build(BuildContext context) {
final animation = listenable as Animation<double>;
return Transform.scale(
scale: animation.value,
child: child,
);
}
}
// Usage
ScaleTransition(
scale: _controller,
child: const ExpensiveWidget(), // Not rebuilt!
)
Limit Simultaneous Animations
// â BAD - Too many simultaneous animations
class MyPage extends StatelessWidget {
Widget build(BuildContext context) {
return Column(
children: List.generate(100, (index) =>
AnimatedContainer(
duration: Duration(seconds: 1),
// Each container animates independently
),
),
);
}
}
// â
GOOD - Stagger animations, limit concurrent
class MyPage extends StatelessWidget {
Widget build(BuildContext context) {
return AnimatedList(
initialItemCount: items.length,
itemBuilder: (context, index, animation) {
return FadeTransition(
opacity: animation,
child: ItemWidget(item: items[index]),
);
},
);
}
}
Profiling and Debugging
Use Flutter DevTools
# Run app in profile mode (not debug!)
flutter run --profile
# Open DevTools
# Press 'w' in terminal or visit: http://localhost:9100
Key DevTools Features:
- Performance: Identify janky frames, long build times
- Memory: Track heap usage, find leaks
- Network: Monitor API calls, payload sizes
- Timeline: Visualize frame rendering
Identify Build Performance Issues
// Add debug print to measure build time
Widget build(BuildContext context) {
final stopwatch = Stopwatch()..start();
final widget = ExpensiveWidget();
if (kDebugMode) {
print('Build took: ${stopwatch.elapsedMilliseconds}ms');
}
return widget;
}
Performance Overlay
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
Widget build(BuildContext context) {
return MaterialApp(
showPerformanceOverlay: true, // Shows FPS graphs
home: HomePage(),
);
}
}
Best Practices Checklist
Widget Optimization
- Use
constconstructors wherever possible - Minimize
Obxscope to smallest widget - Extract complex widgets into separate classes
- Use proper keys for list items
- Avoid unnecessary
setStateorupdate()calls
List Performance
- Always use
ListView.builderfor dynamic lists - Implement pagination for large datasets
- Add
cacheExtentfor smoother scrolling - Use
addAutomaticKeepAlives: falsewhen appropriate
Image Performance
- Use
cached_network_imagefor network images - Specify image dimensions
- Use appropriate image resolutions
- Precache critical images
Memory Management
- Dispose all controllers in
onClose() - Cancel stream subscriptions
- Dispose animation controllers
- Use
lazyPutinstead ofputfor GetX controllers
Code Organization
- Use deferred imports for large features
- Lazy-load controllers with bindings
- Split large files into smaller modules
Animation
- Use
AnimatedWidgetfor custom animations - Limit simultaneous animations to 2-3
- Use
RepaintBoundaryfor expensive widgets - Prefer implicit animations (
AnimatedContainer,AnimatedOpacity)
Profiling
- Test in profile mode (not debug)
- Use Flutter DevTools regularly
- Monitor frame rendering times (< 16ms target)
- Check memory usage with DevTools
- Profile on real devices, not just emulators
Common Performance Anti-Patterns
Anti-Pattern 1: Unnecessary Rebuilds
// â Obx wrapping entire screen
Obx(() => Scaffold(
body: Column(children: [
Text(controller.title.value),
LargeWidget(),
]),
))
// â
Obx only on changing widget
Scaffold(
body: Column(children: [
Obx(() => Text(controller.title.value)),
const LargeWidget(),
]),
)
Anti-Pattern 2: Expensive Build Methods
// â Heavy computation in build
Widget build(BuildContext context) {
final processedData = heavyComputation(rawData); // Runs every build!
return Text(processedData);
}
// â
Compute once, cache result
class MyController extends GetxController {
final _processedData = ''.obs;
void onInit() {
super.onInit();
_processedData.value = heavyComputation(rawData);
}
}
Anti-Pattern 3: Not Disposing Resources
// â Memory leak - controller never disposed
class MyController extends GetxController {
final StreamController<int> _controller = StreamController();
// Missing onClose() to dispose!
}
// â
Proper disposal
class MyController extends GetxController {
final StreamController<int> _controller = StreamController();
void onClose() {
_controller.close();
super.onClose();
}
}
Performance Targets
| Metric | Target | Tool |
|---|---|---|
| Frame time | < 16ms (60 FPS) | DevTools Performance |
| Build time | < 5ms for simple widgets | Debug prints |
| Memory usage | < 100MB typical screen | DevTools Memory |
| App startup | < 2 seconds | Stopwatch |
| Image load | < 1 second | Network tab |
| API response | < 500ms | Network tab |