Flutter Performance Tips
Optimize your Flutter apps for speed and efficiency
⚡ Why Performance Matters?
Performance optimization ensures your Flutter app runs smoothly with fast load times, responsive UI, and efficient resource usage. Good performance leads to better user experience and higher app ratings.
// Optimized widget with const
const Text('Fast Performance');
// Use ListView.builder for efficiency
ListView.builder(itemBuilder: (context, index) => Item());
Performance Optimization Areas
Widget Optimization
Reduce unnecessary widget rebuilds using const constructors, keys, and proper widget structure to minimize rendering overhead and improve frame rates.
List Performance
Use lazy loading with ListView.builder and GridView.builder to render only visible items, reducing memory usage and improving scroll performance.
Image Optimization
Optimize images by caching, resizing, and using appropriate formats to reduce memory consumption and improve loading times across your app.
Build Optimization
Minimize expensive operations in build methods, use compute for heavy tasks, and profile your app to identify performance bottlenecks.
🔹 Widget Performance Tips
🔸 Use Const Constructors
Const widgets are created once and reused, preventing unnecessary rebuilds. This is one of the easiest and most effective optimizations.
// Good: Const widgets don't rebuild
const Text('Hello');
const Icon(Icons.home);
const Padding(
padding: EdgeInsets.all(8.0),
child: Text('Optimized'),
)
// Avoid: Non-const rebuilds every time
Text('Hello');
Icon(Icons.home);
🔸 Minimize Widget Rebuilds
// Bad: Entire widget rebuilds
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
ExpensiveWidget(), // Rebuilds unnecessarily
Text('$counter'),
],
);
}
}
// Good: Only counter rebuilds
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
int counter = 0;
@override
Widget build(BuildContext context) {
return Column(
children: [
const ExpensiveWidget(), // Doesn't rebuild
Text('$counter'),
],
);
}
}
🔸 Use Keys Wisely
// Use keys for list items that can reorder
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
key: ValueKey(items[index].id),
title: Text(items[index].name),
);
},
)
🔹 List Performance Optimization
🔸 Use ListView.builder
ListView.builder creates items on-demand as they scroll into view, using much less memory than creating all items at once.
// Good: Lazy loading
ListView.builder(
itemCount: 1000,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
)
// Avoid: Creates all 1000 items immediately
ListView(
children: List.generate(
1000,
(index) => ListTile(title: Text('Item $index')),
),
)
🔸 Use GridView.builder
// Efficient grid with lazy loading
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(child: Text(items[index]));
},
)
🔸 Add Separators Efficiently
// Use ListView.separated for lists with dividers
ListView.separated(
itemCount: items.length,
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
separatorBuilder: (context, index) => const Divider(),
)
🔹 Image Performance Tips
🔸 Cache Network Images
// Use cached_network_image package
import 'package:cached_network_image/cached_network_image.dart';
CachedNetworkImage(
imageUrl: 'https://example.com/image.jpg',
placeholder: (context, url) => CircularProgressIndicator(),
errorWidget: (context, url, error) => Icon(Icons.error),
// Images are cached automatically
)
🔸 Specify Image Dimensions
Always specify width and height to prevent unnecessary resizing and improve rendering performance.
// Good: Specified dimensions
Image.network(
'url',
width: 200,
height: 200,
fit: BoxFit.cover,
)
// Avoid: No dimensions specified
Image.network('url')
🔸 Use Appropriate Image Formats
- WebP: Best compression, smaller file sizes
- PNG: For images with transparency
- JPEG: For photos without transparency
- SVG: For icons and simple graphics
🔸 Optimize Image Assets
// Provide multiple resolutions
Image.asset(
'assets/images/logo.png',
// Flutter automatically picks:
// logo.png (1x)
// 2.0x/logo.png (2x)
// 3.0x/logo.png (3x)
)
🔹 Build Method Optimization
🔸 Avoid Heavy Operations in Build
// Bad: Expensive operation in build
@override
Widget build(BuildContext context) {
final data = expensiveCalculation(); // Runs every rebuild
return Text(data);
}
// Good: Calculate once in initState
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
late String data;
@override
void initState() {
super.initState();
data = expensiveCalculation(); // Runs once
}
@override
Widget build(BuildContext context) {
return Text(data);
}
}
🔸 Use Compute for Heavy Tasks
Use compute() to run expensive operations in a separate isolate, preventing UI freezing.
import 'package:flutter/foundation.dart';
// Heavy computation function
List processData(List data) {
// Expensive operation
return data.map((e) => e * 2).toList();
}
// Use compute to run in background
Future loadData() async {
final result = await compute(processData, largeDataList);
setState(() {
processedData = result;
});
}
🔸 Avoid Creating Objects in Build
// Bad: Creates new TextStyle every build
@override
Widget build(BuildContext context) {
return Text(
'Hello',
style: TextStyle(fontSize: 20, color: Colors.blue),
);
}
// Good: Reuse const TextStyle
static const textStyle = TextStyle(fontSize: 20, color: Colors.blue);
@override
Widget build(BuildContext context) {
return Text('Hello', style: textStyle);
}
🔹 State Management Performance
🔸 Use Provider Efficiently
// Good: Only rebuild when needed
Consumer(
builder: (context, counter, child) {
return Text('${counter.count}');
},
)
// Avoid: Rebuilds entire widget tree
class MyWidget extends StatelessWidget {
@override
Widget build(BuildContext context) {
final counter = Provider.of(context);
return Column(
children: [
ExpensiveWidget(), // Rebuilds unnecessarily
Text('${counter.count}'),
],
);
}
}
🔸 Use Selector for Specific Values
// Only rebuilds when count changes
Selector(
selector: (context, provider) => provider.count,
builder: (context, count, child) {
return Text('Count: $count');
},
)
🔹 Animation Performance
🔸 Use AnimatedBuilder
// Efficient animation
class MyAnimation extends StatefulWidget {
@override
_MyAnimationState createState() => _MyAnimationState();
}
class _MyAnimationState extends State
with SingleTickerProviderStateMixin {
late AnimationController _controller;
@override
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(seconds: 2),
vsync: this,
)..repeat();
}
@override
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _controller,
builder: (context, child) {
return Transform.rotate(
angle: _controller.value * 2 * 3.14,
child: child,
);
},
child: const Icon(Icons.star), // Doesn't rebuild
);
}
@override
void dispose() {
_controller.dispose();
super.dispose();
}
}
🔸 Use Implicit Animations
// Simple, performant animations
AnimatedContainer(
duration: Duration(milliseconds: 300),
width: isExpanded ? 200 : 100,
height: isExpanded ? 200 : 100,
color: isExpanded ? Colors.blue : Colors.red,
)
AnimatedOpacity(
opacity: isVisible ? 1.0 : 0.0,
duration: Duration(milliseconds: 500),
child: Text('Fade in/out'),
)
🔹 Memory Management
🔸 Dispose Controllers
Always dispose controllers, streams, and listeners to prevent memory leaks.
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
late TextEditingController _controller;
late ScrollController _scrollController;
@override
void initState() {
super.initState();
_controller = TextEditingController();
_scrollController = ScrollController();
}
@override
void dispose() {
_controller.dispose();
_scrollController.dispose();
super.dispose();
}
@override
Widget build(BuildContext context) {
return TextField(controller: _controller);
}
}
🔸 Cancel Streams and Timers
class MyWidget extends StatefulWidget {
@override
_MyWidgetState createState() => _MyWidgetState();
}
class _MyWidgetState extends State {
StreamSubscription? _subscription;
Timer? _timer;
@override
void initState() {
super.initState();
_subscription = stream.listen((data) {
// Handle data
});
_timer = Timer.periodic(Duration(seconds: 1), (timer) {
// Periodic task
});
}
@override
void dispose() {
_subscription?.cancel();
_timer?.cancel();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Container();
}
}
🔹 Profiling and Debugging
🔸 Use Flutter DevTools
Flutter DevTools provides performance profiling, memory analysis, and widget inspection tools.
# Open DevTools
flutter pub global activate devtools
flutter pub global run devtools
# Or run with your app
flutter run --profile
🔸 Check Performance Overlay
// Enable performance overlay
MaterialApp(
showPerformanceOverlay: true,
home: MyHomePage(),
)
// Shows FPS and frame rendering times
🔸 Profile Your App
# Build in profile mode
flutter run --profile
# Build in release mode for final testing
flutter run --release
Important: Never test performance in debug mode. Always use profile or release mode for accurate results.
🔹 Quick Performance Checklist
✅ Before Release:
- Use const constructors wherever possible
- Use ListView.builder for long lists
- Cache and optimize images
- Dispose all controllers and streams
- Avoid expensive operations in build()
- Profile app in release mode
- Test on real devices, not just emulators
- Check memory usage with DevTools
- Minimize widget rebuilds
- Use compute() for heavy computations