Profiling Rebuilds With RepaintRainbow And Performance Overlay
Jan 26, 2026



Summary
Summary
Summary
Summary
This tutorial explains how to use Flutter's Performance Overlay and debugRepaintRainbow to visualize repaint activity and frame-time spikes. Enable the overlay, turn on rainbow repaint debugging, reproduce jank, correlate overlays with flashing regions, and fix issues by scoping state, using RepaintBoundary, and minimizing expensive paints and builds.
This tutorial explains how to use Flutter's Performance Overlay and debugRepaintRainbow to visualize repaint activity and frame-time spikes. Enable the overlay, turn on rainbow repaint debugging, reproduce jank, correlate overlays with flashing regions, and fix issues by scoping state, using RepaintBoundary, and minimizing expensive paints and builds.
This tutorial explains how to use Flutter's Performance Overlay and debugRepaintRainbow to visualize repaint activity and frame-time spikes. Enable the overlay, turn on rainbow repaint debugging, reproduce jank, correlate overlays with flashing regions, and fix issues by scoping state, using RepaintBoundary, and minimizing expensive paints and builds.
This tutorial explains how to use Flutter's Performance Overlay and debugRepaintRainbow to visualize repaint activity and frame-time spikes. Enable the overlay, turn on rainbow repaint debugging, reproduce jank, correlate overlays with flashing regions, and fix issues by scoping state, using RepaintBoundary, and minimizing expensive paints and builds.
Key insights:
Key insights:
Key insights:
Key insights:
Setting Up The Performance Overlay: Show the overlay to view UI and raster frame-time graphs and identify sustained spikes above the 16ms target.
Using RepaintRainbow To Visualize Repaints: Enable debugRepaintRainbowEnabled to see exactly which areas repaint and how frequently.
Profiling Strategies And Interpreting Results: Correlate overlay spikes with repaint flashes to determine whether work is on the UI thread or raster thread.
Minimizing Repaints And Rebuilds: Use const widgets, scoped state, and RepaintBoundary to limit repaint regions and reduce jank.
Practical Debug Workflow: Reproduce the issue, enable both tools, isolate offending subtrees, and iterate with RepaintBoundary and profiling until frame times stabilize.
Introduction
Profiling UI work in Flutter is essential for smooth mobile development. Two lightweight debug tools — the Performance Overlay and Repaint Rainbow — let you correlate visual repaint activity with frame times so you can find the widgets that cause jank. This tutorial shows how to enable both, read results, and reduce unnecessary repaints and rebuilds.
Setting Up The Performance Overlay
The Performance Overlay is the first place to look for frame-time problems. It shows two timeline graphs (UI vs raster) where each frame's cost is visualized; bars above the 16ms line indicate potential jank on 60fps displays. To enable it quickly during development, set the MaterialApp (or WidgetsApp) property showPerformanceOverlay to true, or use the PerformanceOverlay widget directly.
Example: enable overlay at app startup.
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; void main() { debugRepaintRainbowEnabled = true; // see next section runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext c) => MaterialApp( showPerformanceOverlay: true, home: const Scaffold(body: Center(child: Text('Profiling'))), ); }
When the overlay is visible, watch for sustained spikes and their pattern. A single spike can be expensive layout, expensive build, or one-off work; repeated spikes tied to a UI animation or list scroll suggest repeated repaints or layout thrash.
Using RepaintRainbow To Visualize Repaints And Rebuilds
Repaint Rainbow is a debug flag that paints layers that are being repainted with cycling, high-contrast colors. It's not a production tool — it's a debug visualizer that immediately reveals how often parts of the UI repaint.
Enable it by setting debugRepaintRainbowEnabled = true from main(), which will color flashing areas as they repaint. Typical signals:
Persistent flashing on the same area: that subtree repaints very often.
Flashing large regions: you’re invalidating too big a paint area; consider RepaintBoundary to isolate children.
Flashing small widgets: those widgets are frequently marking themselves dirty — perhaps because setState is too broad or because animations are not cached.
Because repaint rainbow shows only paint operations, you should pair it with the Performance Overlay: a repaint spike on the overlay that matches wide flashing on the screen usually means raster work dominates performance. If the overlay shows UI-thread spikes against little repaint activity, look for expensive builds or layouts instead.
Profiling Strategies And Interpreting Results
1) Reproduce the scenario: enable the overlay and repaint rainbow, then perform the interaction (scroll, animate, tap) that reveals jank.
2) Observe correlations: does a long frame in the overlay coincide with a wide repaint rainbow flash? If yes, rasterization is expensive — image decoding, shader work, or large paint operations might be the cause.
3) Is the spike on the UI graph (often the top graph)? Then the cost is in layout, build, or synchronous computation. Use debugPrintRebuildDirtyWidgets (prints rebuilds) and the DevTools widget rebuild profiler to find frequently rebuilding widgets.
4) Narrow down the region: wrap suspect subtrees with RepaintBoundary to isolate repaint cost. If isolating reduces the flashing and the overlay's raster time goes down, you’ve found a paint-heavy subtree.
Minimizing Repaints And Rebuilds
Prefer const constructors and immutable widgets where possible so Flutter can short-circuit rebuilds.
Group independent, frequently-updated parts inside RepaintBoundary to limit repaint footprints.
Cache expensive draw results with RepaintBoundary + Layer caching or use PictureRecorder if you draw repeatedly.
Avoid calling setState on a parent when only a small child needs updating; instead move state down or use ValueListenable/Provider/Bloc to scope updates.
Consider using RenderObjects for highly optimized, custom painting when widgets are repeatedly painted with little change in tree structure.
Example: using RepaintBoundary for an expensive child.
class Counter extends StatefulWidget { const Counter({super.key}); @override State<Counter> createState() => _CounterState(); } class _CounterState extends State<Counter> { int count = 0; @override Widget build(BuildContext context) => Column(children: [ GestureDetector(onTap: () => setState(() => count++), child: Text('$count')), const RepaintBoundary(child: ExpensiveWidget()), ]); }
This keeps ExpensiveWidget from repainting when only the counter changes.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
The Performance Overlay and Repaint Rainbow form a fast, code-first feedback loop for diagnosing UI jank in Flutter mobile development. Use the overlay to spot frame-time spikes and Repaint Rainbow to see where paint work occurs. Combine those visuals with scoped state management and RepaintBoundary to reduce the repaint footprint and improve frame stability. Start by reproducing the problematic interaction, correlate overlay spikes with repaint flashes, then isolate and refactor the offending subtree.
Introduction
Profiling UI work in Flutter is essential for smooth mobile development. Two lightweight debug tools — the Performance Overlay and Repaint Rainbow — let you correlate visual repaint activity with frame times so you can find the widgets that cause jank. This tutorial shows how to enable both, read results, and reduce unnecessary repaints and rebuilds.
Setting Up The Performance Overlay
The Performance Overlay is the first place to look for frame-time problems. It shows two timeline graphs (UI vs raster) where each frame's cost is visualized; bars above the 16ms line indicate potential jank on 60fps displays. To enable it quickly during development, set the MaterialApp (or WidgetsApp) property showPerformanceOverlay to true, or use the PerformanceOverlay widget directly.
Example: enable overlay at app startup.
import 'package:flutter/material.dart'; import 'package:flutter/rendering.dart'; void main() { debugRepaintRainbowEnabled = true; // see next section runApp(const MyApp()); } class MyApp extends StatelessWidget { const MyApp({super.key}); @override Widget build(BuildContext c) => MaterialApp( showPerformanceOverlay: true, home: const Scaffold(body: Center(child: Text('Profiling'))), ); }
When the overlay is visible, watch for sustained spikes and their pattern. A single spike can be expensive layout, expensive build, or one-off work; repeated spikes tied to a UI animation or list scroll suggest repeated repaints or layout thrash.
Using RepaintRainbow To Visualize Repaints And Rebuilds
Repaint Rainbow is a debug flag that paints layers that are being repainted with cycling, high-contrast colors. It's not a production tool — it's a debug visualizer that immediately reveals how often parts of the UI repaint.
Enable it by setting debugRepaintRainbowEnabled = true from main(), which will color flashing areas as they repaint. Typical signals:
Persistent flashing on the same area: that subtree repaints very often.
Flashing large regions: you’re invalidating too big a paint area; consider RepaintBoundary to isolate children.
Flashing small widgets: those widgets are frequently marking themselves dirty — perhaps because setState is too broad or because animations are not cached.
Because repaint rainbow shows only paint operations, you should pair it with the Performance Overlay: a repaint spike on the overlay that matches wide flashing on the screen usually means raster work dominates performance. If the overlay shows UI-thread spikes against little repaint activity, look for expensive builds or layouts instead.
Profiling Strategies And Interpreting Results
1) Reproduce the scenario: enable the overlay and repaint rainbow, then perform the interaction (scroll, animate, tap) that reveals jank.
2) Observe correlations: does a long frame in the overlay coincide with a wide repaint rainbow flash? If yes, rasterization is expensive — image decoding, shader work, or large paint operations might be the cause.
3) Is the spike on the UI graph (often the top graph)? Then the cost is in layout, build, or synchronous computation. Use debugPrintRebuildDirtyWidgets (prints rebuilds) and the DevTools widget rebuild profiler to find frequently rebuilding widgets.
4) Narrow down the region: wrap suspect subtrees with RepaintBoundary to isolate repaint cost. If isolating reduces the flashing and the overlay's raster time goes down, you’ve found a paint-heavy subtree.
Minimizing Repaints And Rebuilds
Prefer const constructors and immutable widgets where possible so Flutter can short-circuit rebuilds.
Group independent, frequently-updated parts inside RepaintBoundary to limit repaint footprints.
Cache expensive draw results with RepaintBoundary + Layer caching or use PictureRecorder if you draw repeatedly.
Avoid calling setState on a parent when only a small child needs updating; instead move state down or use ValueListenable/Provider/Bloc to scope updates.
Consider using RenderObjects for highly optimized, custom painting when widgets are repeatedly painted with little change in tree structure.
Example: using RepaintBoundary for an expensive child.
class Counter extends StatefulWidget { const Counter({super.key}); @override State<Counter> createState() => _CounterState(); } class _CounterState extends State<Counter> { int count = 0; @override Widget build(BuildContext context) => Column(children: [ GestureDetector(onTap: () => setState(() => count++), child: Text('$count')), const RepaintBoundary(child: ExpensiveWidget()), ]); }
This keeps ExpensiveWidget from repainting when only the counter changes.
Vibe Studio

Vibe Studio, powered by Steve’s advanced AI agents, is a revolutionary no-code, conversational platform that empowers users to quickly and efficiently create full-stack Flutter applications integrated seamlessly with Firebase backend services. Ideal for solo founders, startups, and agile engineering teams, Vibe Studio allows users to visually manage and deploy Flutter apps, greatly accelerating the development process. The intuitive conversational interface simplifies complex development tasks, making app creation accessible even for non-coders.
Conclusion
The Performance Overlay and Repaint Rainbow form a fast, code-first feedback loop for diagnosing UI jank in Flutter mobile development. Use the overlay to spot frame-time spikes and Repaint Rainbow to see where paint work occurs. Combine those visuals with scoped state management and RepaintBoundary to reduce the repaint footprint and improve frame stability. Start by reproducing the problematic interaction, correlate overlay spikes with repaint flashes, then isolate and refactor the offending subtree.
Build Flutter Apps Faster with Vibe Studio
Vibe Studio is your AI-powered Flutter development companion. Skip boilerplate, build in real-time, and deploy without hassle. Start creating apps at lightning speed with zero setup.
Other Insights






















