Performance in Flutter is rarely a single silver bullet. High‑performing apps apply a repeatable workflow: measure, localize, fix, prevent regressions. This guide provides a battle‑tested playbook for diagnosing and improving startup time, jank, memory, and network performance in production‑scale Flutter apps.
Contents
- Principles and workflow
- Startup time: cold/warm launch
- Jank: frame scheduling, raster vs. UI thread
- Memory: leaks, caches, image use
- Network: bandwidth, latency, and serialization
- Build size and AOT optimizations
- Prevent regressions: CI/CD and guardrails
- Field checklist
Principles and workflow
-
Reproduce and measure
- Always attach metrics to symptoms: “jank” → frame time spikes; “slow” → startup time (TTI), API RTTs, list scroll FPS.
- Use stable test devices and capture baselines before changes.
-
Instrument and localize
- Use the Flutter DevTools suite (Timeline, CPU, Memory) and platform tools (Android Studio Profiler, Xcode Instruments).
- Mark hypothesis boundaries in code (Timeline events) to isolate hot sections.
-
Fix narrow causes, not symptoms
- Eliminate redundant layout, avoid overdraw, reduce object churn, batch network and disk I/O.
-
Automate prevention
- Add performance checks into CI (golden perf tests, size budgets, lint rules); rollouts with monitoring and alerting.
Startup time (cold/warm launch) Symptoms: splash hangs, first paint delays, delayed TTI (time to interactive)
Measure:
- Android: adb shell am start -W …, Android Studio “App Startup” profiler
- iOS: “Time Profiler” + “System Trace” in Instruments
- Flutter DevTools: “App Start” events in Timeline, first frame/first useful paint markers
Common causes and fixes:
- Too much work in main(): Lazy initialize analytics, logging, crash reporters; defer non‑critical singletons.
- Heavy sync work on UI thread: Move blocking I/O to Isolates or compute(); prefetch after first frame.
- Expensive widget trees at first build: Defer non‑critical subtrees with placeholders; use const constructors and avoid deep synchronous init in build().
- Asset decode on first frame: Precache images after first frame; defer large vector/bitmap decoding.
- Dart AOT and code size: Strip unused assets; tree‑shake icons; split flavors and targets for minimal includes.
Example defer pattern:
void main() {
WidgetsFlutterBinding.ensureInitialized();
runApp(const App());
WidgetsBinding.instance.addPostFrameCallback((_) {
// Defer non-critical init to after first frame
unawaited(_bootstrapLateInit());
});
}
Future<void> _bootstrapLateInit() async {
// Analytics, remote config warmup, caches
}
Jank (UI vs. raster thread) Symptoms: stutters during scrolling/animations, frame drops
Understand the pipeline:
- UI thread (Dart): builds widgets, runs animations, layout, and schedules frames
- Raster thread (C++/Skia): draws layers, GPU uploads; heavy shaders or large textures cause stalls
Tools:
- Flutter DevTools → Performance → Record profile → Inspect frames (UI/Raster durations)
- Shader compilation: Profile in release/profile modes; enable “SkSL warmup” capture on Android
- “Performance Overlay” (WidgetsApp.showPerformanceOverlay = true) to visualize frame budget
Top offenders and remedies:
- Expensive build/layout: