Performance problems in Flutter fall into three categories: unnecessary rebuilds, heavy rendering, and slow startup. Here's how to find and fix each.
1. Stop Unnecessary Rebuilds
This is the most common source of Flutter jank. setState triggers a rebuild of the entire subtree below the widget that called it. The fix is making widgets as small as possible and using const everywhere you can.
Use const constructors aggressively:
// Bad — rebuilds every time parent rebuilds
child: Text('Hello World'),
// Good — Flutter skips this entirely on rebuild
child: const Text('Hello World'),If a widget has no dynamic data, it should be const. The Dart analyser will tell you when you've missed one.
Extract heavy subtrees into separate widgets:
// Bad — entire screen rebuilds when counter changes
class HomeScreen extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
HeavyStaticWidget(), // rebuilt unnecessarily
Text('$counter'),
],
);
}
}
// Good — only the counter text rebuilds
class HomeScreen extends StatefulWidget {
@override
Widget build(BuildContext context) {
return Column(
children: [
const HeavyStaticWidget(), // never rebuilt
CounterText(counter: counter),
],
);
}
}Use RepaintBoundary for isolated animations:
RepaintBoundary(
child: AnimatedWidget(), // only this layer repaints
)2. Profile Before You Optimise
Never guess. Open DevTools and look at the actual frame timeline.
flutter run --profileIn Flutter DevTools → Performance tab, enable "Track widget rebuilds". Any widget showing a red rebuild count on every frame is a candidate for optimisation.
The two expensive operations to watch for:
- Raster thread spike → you're doing heavy painting or compositing
- UI thread spike → you're doing heavy Dart work on the main thread
3. Fix ListView Performance
If you have a list of more than ~20 items, never use ListView with a children array — it renders all items upfront.
// Bad — builds all 1000 items immediately
ListView(
children: items.map((item) => ItemWidget(item)).toList(),
)
// Good — builds only visible items
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) => ItemWidget(items[index]),
)For lists with mixed item heights, add itemExtent if possible — it lets Flutter skip layout calculations for off-screen items.
4. Image Optimisation
Images are the most common cause of memory pressure in Flutter apps.
// Always specify dimensions — Flutter can resize at decode time
Image.network(
url,
width: 200,
height: 200,
cacheWidth: 400, // 2x for retina, avoids over-fetching
cacheHeight: 400,
)For local assets, use the flutter_image_compress package to compress before saving. For network images, cached_network_image handles caching and fading automatically.
5. Move Work Off the Main Thread
The UI thread should only do UI work. Heavy computation (JSON parsing, image processing, crypto) blocks it.
Use compute() for one-off heavy tasks:
final result = await compute(parseHeavyJson, rawJsonString);For ongoing background work, use Isolate:
final receivePort = ReceivePort();
await Isolate.spawn(backgroundTask, receivePort.sendPort);6. Reduce Startup Time
App startup time is a first impression. Three things that help:
Defer initialisation:
void main() {
runApp(const MyApp()); // show UI immediately
// Initialise non-critical services after first frame
WidgetsBinding.instance.addPostFrameCallback((_) {
Analytics.init();
PushNotifications.init();
});
}Avoid doing async work in main() before runApp. If you must (e.g., loading settings), show a lightweight splash widget immediately and load data after.
Use deferred loading for large features:
import 'package:my_app/heavy_feature.dart' deferred as heavyFeature;
// Only downloads and compiles when needed
await heavyFeature.loadLibrary();
heavyFeature.HeavyFeatureScreen();7. Check Shader Compilation Jank
First-time animation jank on older devices is often shader compilation — the GPU compiling shaders on first use. Impeller (Flutter's new renderer, default on iOS and Android) largely eliminates this, but if you're still seeing first-run jank:
flutter run --enable-impellerIf you're stuck on the old Skia renderer, pre-warm shaders:
flutter run --cache-sksl
# Then export and bundle the shader cacheThe 80/20 of Flutter Performance
- Add
constto every widget that doesn't have dynamic data - Use
ListView.builderinstead ofListViewwith children - Profile with DevTools before assuming you know the bottleneck
- Move heavy Dart work to
compute()orIsolate - Specify image dimensions so Flutter can resize at decode time
In my experience, steps 1 and 2 alone fix 60% of Flutter performance issues.