How to check jank in Flutter and how to avoid it in future?
Answer
Overview
Jank is any visible stutter, freeze, or jerky motion in the UI caused by frames that take longer than 16.67ms (at 60fps) to render. When a frame misses its deadline, the previous frame is displayed again, making animations and scrolling appear choppy.
How to Detect Jank
1. Performance Overlay
bashflutter run --profile # Press P to toggle the overlay
- Red bars in bottom graph (UI thread) = Dart code is too slow
- Red bars in top graph (Raster thread) = Rendering is too complex
- Any bar exceeding the white 16ms line = dropped frame = jank
2. DevTools Performance Tab
text1. Run: flutter run --profile 2. Open DevTools > Performance tab 3. Click "Record" and interact with your app 4. Janky frames appear as tall bars exceeding the 16ms line 5. Click individual frames to see build, layout, paint breakdown 6. Shader compilation jank appears in dark red
3. Programmatic Detection
dartimport 'package:flutter/scheduler.dart'; void detectJank() { SchedulerBinding.instance.addTimingsCallback(( List<FrameTiming> timings, ) { for (final timing in timings) { final totalMs = timing.totalSpan.inMilliseconds; if (totalMs > 16) { debugPrint('JANK! Frame took ${totalMs}ms ' '(build: ${timing.buildDuration.inMilliseconds}ms, ' 'raster: ${timing.rasterDuration.inMilliseconds}ms)'); } } }); }
Always profile on a real device in profile mode. Debug mode and emulators give misleading results.
Common Causes and How to Fix
Cause 1: Heavy Computation on Main Thread
dart// ❌ BAD — Blocks UI thread for 200ms void loadData() { final result = heavyJsonParsing(rawData); // Blocking! setState(() => data = result); }
dart// ✅ GOOD — Offload to Isolate import 'package:flutter/foundation.dart'; Future<void> loadData() async { // compute() runs in a separate Isolate final result = await compute(heavyJsonParsing, rawData); setState(() => data = result); } // Or using Isolate.run (Dart 2.19+) Future<void> loadData() async { final result = await Isolate.run(() { return heavyJsonParsing(rawData); }); setState(() => data = result); }
Cause 2: Excessive Widget Rebuilds
dart// ❌ BAD — Entire list rebuilt on every frame ListView( children: items.map((item) => ExpensiveWidget(item: item)).toList(), ); // ✅ GOOD — Lazy building, only visible items built ListView.builder( itemCount: items.length, itemBuilder: (context, index) => ExpensiveWidget( item: items[index], ), );
dart// ❌ BAD — setState rebuilds everything class _DashboardState extends State<Dashboard> { int counter = 0; Widget build(BuildContext context) { return Column( children: [ HeavyChart(), // Rebuilds unnecessarily! HeavyHeader(), // Rebuilds unnecessarily! Text('$counter'), ElevatedButton( onPressed: () => setState(() => counter++), child: const Text('+'), ), ], ); } }
dart// ✅ GOOD — const widgets skip rebuild class _DashboardState extends State<Dashboard> { int counter = 0; Widget build(BuildContext context) { return Column( children: [ const HeavyChart(), // Skipped! (const) const HeavyHeader(), // Skipped! (const) Text('$counter'), ElevatedButton( onPressed: () => setState(() => counter++), child: const Text('+'), ), ], ); } }
Cause 3: Repaint Scope Too Large
dart// ✅ GOOD — Isolate frequently repainting widgets RepaintBoundary( child: AnimatedWidget( // Only this subtree repaints on animation tick // Rest of the screen is NOT repainted ), )
Cause 4: Shader Compilation Jank
First-time shader compilation can take 20-200ms per shader.
Fix Option A: Use Impeller (recommended)
Impeller pre-compiles all shaders AOT, eliminating runtime shader jank entirely.
bash# Impeller is default on iOS (Flutter 3.16+) and Android (Flutter 3.22+) # To explicitly enable: flutter run --enable-impeller
Fix Option B: SkSL Warm-Up (for Skia backend)
bash# Step 1: Capture shaders during a test run flutter run --profile --cache-sksl --purge-persistent-cache # Step 2: Exercise ALL animations in the app # Step 3: Press M in terminal to export shaders # Saves to: flutter_01.sksl.json # Step 4: Build with shader bundle flutter build apk --bundle-sksl-path flutter_01.sksl.json flutter build ios --bundle-sksl-path flutter_01.sksl.json
Cause 5: Expensive saveLayer Operations
dart// ❌ BAD — Opacity triggers saveLayer on entire subtree Opacity( opacity: 0.5, child: ComplexWidgetTree(), // Expensive saveLayer! ) // ✅ GOOD — Use AnimatedOpacity or FadeTransition AnimatedOpacity( opacity: 0.5, duration: const Duration(milliseconds: 300), child: ComplexWidgetTree(), )
Cause 6: Large Image Decoding
dart// ❌ BAD — Full-resolution image decoded on main thread Image.network('https://example.com/4000x3000.jpg') // ✅ GOOD — Resize at decode time + pre-cache Image.network( 'https://example.com/4000x3000.jpg', cacheWidth: 400, // Decode at display size cacheHeight: 300, ) // Pre-cache before navigating precacheImage( NetworkImage('https://example.com/hero.jpg'), context, );
Jank Prevention Checklist
| Action | Impact |
|---|---|
| Use text | High |
| Use text text | High |
| Offload heavy work to Isolates via text | Very High |
| Wrap animated widgets in text | Medium |
| Avoid text | Medium |
| Pre-cache images with text | Medium |
| Use Impeller renderer (default on modern Flutter) | Very High |
| Use text text | Medium |
| Always test in profile mode on physical devices | Critical |
| Avoid synchronous I/O on the main thread | High |
Quick Diagnostic Flow
textApp feels janky? │ ├─ Run: flutter run --profile ├─ Enable Performance Overlay (press P) │ ├─ Red bars in BOTTOM graph (UI thread)? │ ├─ Heavy build() method → simplify, use const │ ├─ Heavy computation → use compute() / Isolate │ └─ Too many rebuilds → targeted setState, const widgets │ ├─ Red bars in TOP graph (Raster thread)? │ ├─ Shader compilation → enable Impeller or SkSL warm-up │ ├─ Complex painting → use RepaintBoundary │ └─ saveLayer (Opacity, ClipPath) → use alternatives │ └─ Both graphs red? └─ Widget tree too complex → simplify, split into smaller widgets
Golden Rule: Profile early, profile often. Don't wait until the app is complete to check for jank — catch it during development.
Learn more at Flutter Performance Profiling and Shader Compilation Jank.