307 lines
6.5 KiB
Markdown
307 lines
6.5 KiB
Markdown
|
|
# Performance Optimization
|
||
|
|
|
||
|
|
Flutter performance guide covering profiling, const optimization, and DevTools analysis.
|
||
|
|
|
||
|
|
## Profiling Commands
|
||
|
|
|
||
|
|
```bash
|
||
|
|
# Run in profile mode (required for accurate measurements)
|
||
|
|
flutter run --profile
|
||
|
|
|
||
|
|
# Analyze code issues
|
||
|
|
flutter analyze
|
||
|
|
|
||
|
|
# Launch DevTools
|
||
|
|
flutter pub global activate devtools
|
||
|
|
flutter pub global run devtools
|
||
|
|
|
||
|
|
# Build release for testing
|
||
|
|
flutter build apk --release
|
||
|
|
flutter build ios --release
|
||
|
|
```
|
||
|
|
|
||
|
|
## Const Widget Optimization
|
||
|
|
|
||
|
|
The most important optimization for preventing unnecessary rebuilds:
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// BAD - Creates new objects every build
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Container(
|
||
|
|
padding: EdgeInsets.all(16), // New object each time
|
||
|
|
child: Text('Hello'), // New widget each time
|
||
|
|
);
|
||
|
|
}
|
||
|
|
|
||
|
|
// GOOD - Const prevents rebuilds
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Container(
|
||
|
|
padding: const EdgeInsets.all(16),
|
||
|
|
child: const Text('Hello'),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Extracting Const Widgets
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// BAD - Inline static content
|
||
|
|
class MyScreen extends StatelessWidget {
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return Column(
|
||
|
|
children: [
|
||
|
|
Icon(Icons.star, size: 48),
|
||
|
|
Text('Welcome'),
|
||
|
|
Text('Description text here'),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// GOOD - Extract to const classes
|
||
|
|
class MyScreen extends StatelessWidget {
|
||
|
|
const MyScreen({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return const Column(
|
||
|
|
children: [
|
||
|
|
_Header(),
|
||
|
|
_Description(),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
class _Header extends StatelessWidget {
|
||
|
|
const _Header();
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) {
|
||
|
|
return const Column(
|
||
|
|
children: [
|
||
|
|
Icon(Icons.star, size: 48),
|
||
|
|
Text('Welcome'),
|
||
|
|
],
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Selective Provider Watching
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// BAD - Rebuilds on any user change
|
||
|
|
class UserAvatar extends ConsumerWidget {
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
||
|
|
final user = ref.watch(userProvider);
|
||
|
|
return CircleAvatar(
|
||
|
|
backgroundImage: NetworkImage(user.avatarUrl),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// GOOD - Only rebuilds when avatarUrl changes
|
||
|
|
class UserAvatar extends ConsumerWidget {
|
||
|
|
const UserAvatar({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context, WidgetRef ref) {
|
||
|
|
final avatarUrl = ref.watch(userProvider.select((u) => u.avatarUrl));
|
||
|
|
return CircleAvatar(
|
||
|
|
backgroundImage: NetworkImage(avatarUrl),
|
||
|
|
);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## RepaintBoundary
|
||
|
|
|
||
|
|
Isolate expensive widgets to prevent unnecessary repaints:
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// Isolate complex animated widgets
|
||
|
|
RepaintBoundary(
|
||
|
|
child: ComplexAnimatedWidget(),
|
||
|
|
)
|
||
|
|
|
||
|
|
// Isolate frequently updating widgets
|
||
|
|
RepaintBoundary(
|
||
|
|
child: StreamBuilder<int>(
|
||
|
|
stream: counterStream,
|
||
|
|
builder: (context, snapshot) => Text('${snapshot.data}'),
|
||
|
|
),
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
## List Optimization
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// BAD - Builds all items upfront
|
||
|
|
ListView(
|
||
|
|
children: items.map((item) => ItemWidget(item: item)).toList(),
|
||
|
|
)
|
||
|
|
|
||
|
|
// GOOD - Lazy loading with builder
|
||
|
|
ListView.builder(
|
||
|
|
itemCount: items.length,
|
||
|
|
itemBuilder: (context, index) {
|
||
|
|
return ItemWidget(
|
||
|
|
key: ValueKey(items[index].id),
|
||
|
|
item: items[index],
|
||
|
|
);
|
||
|
|
},
|
||
|
|
)
|
||
|
|
|
||
|
|
// For heterogeneous content
|
||
|
|
ListView.separated(
|
||
|
|
itemCount: items.length,
|
||
|
|
separatorBuilder: (_, __) => const Divider(),
|
||
|
|
itemBuilder: (context, index) => ItemWidget(item: items[index]),
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
## Image Optimization
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// Use cached_network_image for network images
|
||
|
|
CachedNetworkImage(
|
||
|
|
imageUrl: url,
|
||
|
|
placeholder: (_, __) => const ShimmerPlaceholder(),
|
||
|
|
errorWidget: (_, __, ___) => const Icon(Icons.error),
|
||
|
|
memCacheWidth: 200,
|
||
|
|
memCacheHeight: 200,
|
||
|
|
)
|
||
|
|
|
||
|
|
// Resize images in memory
|
||
|
|
Image.network(
|
||
|
|
url,
|
||
|
|
cacheWidth: 200, // Decode at smaller size
|
||
|
|
cacheHeight: 200, // Saves memory
|
||
|
|
)
|
||
|
|
|
||
|
|
// Precache images
|
||
|
|
precacheImage(NetworkImage(url), context);
|
||
|
|
```
|
||
|
|
|
||
|
|
## Heavy Computation
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// BAD - Blocks UI thread
|
||
|
|
void processData() {
|
||
|
|
final result = heavyComputation(data); // UI freezes
|
||
|
|
updateUI(result);
|
||
|
|
}
|
||
|
|
|
||
|
|
// GOOD - Run in isolate
|
||
|
|
Future<void> processData() async {
|
||
|
|
final result = await compute(heavyComputation, data);
|
||
|
|
updateUI(result);
|
||
|
|
}
|
||
|
|
|
||
|
|
// For multiple operations
|
||
|
|
Future<void> processMultiple() async {
|
||
|
|
final results = await Future.wait([
|
||
|
|
compute(process1, data1),
|
||
|
|
compute(process2, data2),
|
||
|
|
compute(process3, data3),
|
||
|
|
]);
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
## Animation Performance
|
||
|
|
|
||
|
|
```dart
|
||
|
|
// Use AnimatedBuilder for custom animations
|
||
|
|
AnimatedBuilder(
|
||
|
|
animation: controller,
|
||
|
|
builder: (context, child) {
|
||
|
|
return Transform.rotate(
|
||
|
|
angle: controller.value * 2 * pi,
|
||
|
|
child: child, // Child not rebuilt
|
||
|
|
);
|
||
|
|
},
|
||
|
|
child: const ExpensiveWidget(),
|
||
|
|
)
|
||
|
|
|
||
|
|
// Prefer implicit animations for simple cases
|
||
|
|
AnimatedContainer(
|
||
|
|
duration: const Duration(milliseconds: 300),
|
||
|
|
width: expanded ? 200 : 100,
|
||
|
|
child: const Content(),
|
||
|
|
)
|
||
|
|
```
|
||
|
|
|
||
|
|
## DevTools Analysis
|
||
|
|
|
||
|
|
### Key Metrics
|
||
|
|
|
||
|
|
| Metric | Target | Action if Exceeded |
|
||
|
|
|--------|--------|-------------------|
|
||
|
|
| Frame time | < 16ms (60fps) | Profile build/paint |
|
||
|
|
| Build time | < 8ms | Add const, extract widgets |
|
||
|
|
| Paint time | < 8ms | Add RepaintBoundary |
|
||
|
|
| Memory | Stable | Check for leaks |
|
||
|
|
|
||
|
|
### Common Issues
|
||
|
|
|
||
|
|
| Issue | Symptom | Solution |
|
||
|
|
|-------|---------|----------|
|
||
|
|
| Expensive builds | High build time | Extract const widgets |
|
||
|
|
| Excessive repaints | High paint time | Add RepaintBoundary |
|
||
|
|
| Memory leaks | Growing memory | Dispose controllers |
|
||
|
|
| Jank | Dropped frames | Use compute() |
|
||
|
|
|
||
|
|
## Performance Checklist
|
||
|
|
|
||
|
|
| Check | Solution |
|
||
|
|
|-------|----------|
|
||
|
|
| Unnecessary rebuilds | Add `const`, use `select()` |
|
||
|
|
| Large lists | Use `ListView.builder` |
|
||
|
|
| Image loading | Use `cached_network_image` |
|
||
|
|
| Heavy computation | Use `compute()` |
|
||
|
|
| Jank in animations | Use `RepaintBoundary` |
|
||
|
|
| Memory leaks | Dispose controllers, cancel subscriptions |
|
||
|
|
| Network calls | Cache responses, debounce requests |
|
||
|
|
| Startup time | Defer initialization, lazy loading |
|
||
|
|
|
||
|
|
## Dispose Pattern
|
||
|
|
|
||
|
|
```dart
|
||
|
|
class MyWidget extends StatefulWidget {
|
||
|
|
const MyWidget({super.key});
|
||
|
|
|
||
|
|
@override
|
||
|
|
State<MyWidget> createState() => _MyWidgetState();
|
||
|
|
}
|
||
|
|
|
||
|
|
class _MyWidgetState extends State<MyWidget> {
|
||
|
|
late final TextEditingController _controller;
|
||
|
|
late final StreamSubscription _subscription;
|
||
|
|
|
||
|
|
@override
|
||
|
|
void initState() {
|
||
|
|
super.initState();
|
||
|
|
_controller = TextEditingController();
|
||
|
|
_subscription = stream.listen(handleData);
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
void dispose() {
|
||
|
|
_controller.dispose();
|
||
|
|
_subscription.cancel();
|
||
|
|
super.dispose();
|
||
|
|
}
|
||
|
|
|
||
|
|
@override
|
||
|
|
Widget build(BuildContext context) => Container();
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
*Flutter and DevTools are trademarks of Google LLC.*
|
||
|
|
|