Flutter Performance Optimization
Users judge an app by how smooth it feels. A stutter during scrolling or a slow screen change makes an app seem broken, even when every feature works. Flutter aims for 60 frames per second, which gives each frame about 16 milliseconds. Many modern phones run at 90 or 120 frames per second and allow even less time.
The Frame Budget
One frame = 16 ms budget (60 fps)
├──── build ────┼── layout ──┼── paint ──┤
│ 5 ms │ 4 ms │ 3 ms │ = 12 ms ✔ smooth
├─────────── build ──────────┼─ layout ─┼─ paint ─┤
│ 20 ms │ 4 ms │ 3 ms │ = 27 ms ✘ dropped frame
A dropped frame appears on screen as a brief freeze called jank. Optimization work keeps every frame inside its budget.
Measure Before You Change Anything
Guessing wastes time. Measure first.
- Run the app in profile mode on a real phone:
flutter run --profile. - Open the Performance view in DevTools and look for red frames.
- Turn on the performance overlay with
showPerformanceOverlay: trueinMaterialApp.
Debug mode runs much slower than release mode, so never judge speed there.
Use const Widgets
A const widget is built once and reused forever. Flutter skips it during rebuilds.
// Better
const Text('Welcome');
const SizedBox(height: 16);
const Icon(Icons.home);
Add const wherever the analyzer suggests it. This habit costs nothing and removes thousands of needless rebuilds.
Rebuild Less
Calling setState rebuilds the whole widget that owns it. Large widgets that rebuild every second waste effort. Keep changing parts small and separate.
Heavy: one big screen rebuilds when the counter changes
┌───────────────────────────────┐
│ Header List Footer Count │ ← all rebuild
└───────────────────────────────┘
Light: only the counter widget rebuilds
┌───────────────────────────────┐
│ Header List Footer [Count]│ ← only [Count] rebuilds
└───────────────────────────────┘
Move the changing part into its own small widget with its own state. Tools such as BlocBuilder, Consumer, and ValueListenableBuilder help rebuild narrow sections.
Build Lists Lazily
Use ListView.builder and GridView.builder for long collections. These widgets create only the visible rows. A Column inside a SingleChildScrollView builds every child at once, which hurts performance for large lists.
Keep build() Light
Flutter may call build many times per second. Avoid sorting a big list, parsing JSON, or reading files inside it. Calculate once and store the result.
// Slow: sorts on every rebuild
Widget build(BuildContext context) {
final sorted = items..sort();
return ListView( /* ... */ );
}
// Better: sort once in initState or when data changes
Move Heavy Work Off the Main Thread
Dart runs UI code on one thread. A long calculation freezes the screen. The compute function runs a task in a separate isolate, a worker with its own memory.
import 'package:flutter/foundation.dart';
List<int> sortLargeList(List<int> data) {
data.sort();
return data;
}
final result = await compute(sortLargeList, bigList);
Main isolate (UI) Worker isolate
┌──────────────┐ send data ┌──────────────┐
│ draws frames │ ──────────→ │ heavy work │
│ stays smooth │ ←────────── │ │
└──────────────┘ result └──────────────┘
Parsing a large JSON response is a classic case for compute.
Handle Images Carefully
- Resize images on the server instead of loading a 4000-pixel photo into a 100-pixel thumbnail.
- Set
cacheWidthorcacheHeightonImageto decode a smaller version. - Use the
cached_network_imagepackage to avoid downloading the same picture twice. - Prefer compressed formats such as WebP.
Limit Expensive Effects
Widgets such as Opacity, ClipRRect, and BackdropFilter force extra drawing passes. Use them sparingly, and prefer a color with an alpha value over an Opacity widget around a large subtree.
Wrap an animated section in RepaintBoundary to keep its repainting from affecting its neighbors.
Use Keys for Reorderable Lists
Give list items a ValueKey tied to a unique id. Flutter matches each widget to its data after a reorder and avoids rebuilding everything.
App Size and Startup
- Remove unused packages from
pubspec.yaml. - Build with
flutter build apk --split-per-abior an app bundle for smaller downloads. - Delay loading heavy features until the user opens them.
Quick Checklist
| Symptom | Likely Cause | Remedy |
|---|---|---|
| Janky scrolling | Non-lazy list or heavy items | Use builder constructors |
| Slow screen open | Work inside build | Move work to initState or compute |
| High memory use | Large images | Resize and cache |
| Whole screen flickers on tap | Wide setState | Split into smaller widgets |
Practice Ideas
Take a scrolling list of one thousand rows and record a profile run in DevTools. Replace a plain Column with ListView.builder and compare the frame chart. Move a heavy JSON parse into compute and watch the red frames disappear. Measure again after each change, so you learn which fixes matter most.
