Flutter Performance Optimisation: A Practical Guide
·4 min read
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 rebuildschild: Text('Hello World'),// Good — Flutter skips this entirely on rebuildchild: 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 changesclass HomeScreen extends StatefulWidget {
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 --profile
In 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 immediatelyListView( children: items.map((item) => ItemWidget(item)).toList(),)// Good — builds only visible itemsListView.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 timeImage.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 neededawait 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-impeller
If you're stuck on the old Skia renderer, pre-warm shaders:
flutter run --cache-sksl# Then export and bundle the shader cache
The 80/20 of Flutter Performance
Add const to every widget that doesn't have dynamic data
Use ListView.builder instead of ListView with children
Profile with DevTools before assuming you know the bottleneck
Move heavy Dart work to compute() or Isolate
Specify image dimensions so Flutter can resize at decode time
In my experience, steps 1 and 2 alone fix 60% of Flutter performance issues.