Lesson 27 min

Memory & Performance Optimization

00:00 / 00:00

The Performance Mindset

Premature optimization is the root of all evil — but ignorant optimization is catastrophic. In Flutter, performance problems manifest as jank (dropped frames), slow startup, and memory bloat. Here's how to identify and fix them systematically.

Profile first, optimize second. Never guess where the bottleneck is — measure it with Dart DevTools.

The Flutter Performance Tiers

  • 60fps / 120fps target — Each frame has 16.67ms / 8.33ms budget
  • UI thread — Dart code, layout, painting decisions
  • Raster thread — GPU, compositing (C++, not Dart)
  • Jank happens when either thread exceeds its time budget

Memory Optimization Techniques

dart
// 1. const constructors — zero runtime allocation const myPadding = EdgeInsets.symmetric(horizontal: 16, vertical: 8); const myStyle = TextStyle(fontSize: 14, fontWeight: FontWeight.w600); // 2. Cache expensive computations class ExpensiveWidget extends StatelessWidget { // Compute once, reuse across builds static final _expensiveData = _computeOnce(); static List<String> _computeOnce() { // Heavy computation... return processData(); } } // 3. Dispose resources properly class MyState extends State<MyWidget> { late final AnimationController _controller; StreamSubscription? _subscription; void initState() { super.initState(); _controller = AnimationController(vsync: this, duration: 300.ms); _subscription = dataStream.listen(_onData); } void dispose() { _controller.dispose(); // Always dispose! _subscription?.cancel(); super.dispose(); } } // 4. Use ListView.builder for long lists — renders only visible items ListView.builder( itemCount: items.length, itemBuilder: (context, index) => ItemCard(item: items[index]), ) // vs ListView(children: items.map(ItemCard.new).toList()) // ^ This creates ALL items at once — terrible for performance

CPU Optimization Techniques

dart
// 1. Avoid rebuilding unchanged widgets with const class MyApp extends StatelessWidget { Widget build(BuildContext context) { return Column(children: [ const ExpensiveHeader(), // const = never rebuilt DynamicContent(data: data), ]); } } // 2. Avoid expensive work in build() // BAD Widget build(BuildContext context) { final sorted = items.toList()..sort(); // O(n log n) on every build! return ListView.builder(itemCount: sorted.length, ...); } // GOOD — sort once, in state void initState() { super.initState(); _sortedItems = widget.items.toList()..sort(); } // 3. Debounce expensive operations (search, API calls) Timer? _debounce; void onSearchChanged(String query) { _debounce?.cancel(); _debounce = Timer(const Duration(milliseconds: 300), () { performSearch(query); }); } // 4. Use RepaintBoundary to isolate expensive subtrees RepaintBoundary( child: ComplexAnimatedWidget(), // only repaints this subtree )

Profiling with DevTools

bash
# Always profile in PROFILE mode — not debug! flutter run --profile # Key DevTools tabs: # - Performance: CPU flame graphs, frame timing # - Memory: heap snapshots, leak detection # - CPU Profiler: function-level hotspots

Identifying Memory Leaks

dart
// Common leak: holding a reference in a static variable class LeakyBloc { // WRONG: static list grows forever static final List<LeakyBloc> _instances = []; LeakyBloc() { _instances.add(this); // Never removed! } } // Fix: use WeakReference for non-owning references final ref = WeakReference(someObject); ref.target; // null if object was GC'd

Summary

Dart performance optimization has a clear hierarchy: use const everywhere, avoid unnecessary allocations in hot paths, dispose resources diligently, use lazy loading for large datasets, and profile with DevTools before optimizing. The biggest wins usually come from architectural changes (e.g., switching from ListView to ListView.builder), not micro-optimizations.

WhatsApp