skip to content

Opening a Flutter photo-editor screen drops frames for the first second; how do you use DevTools' timeline, Track Layouts and Track Paints to find what is slow?

level: seniorimportance: should knowfreq 38%

answer

  1. record the open in profile mode
  2. select the red frames, read Frame analysis
  3. BUILD, LAYOUT, PAINT events in the timeline
  4. Track Layouts / Paints name render objects
  5. tracing inflates times; compare shapes

basics

~20 s

Record opening the screen in profile mode, select the red frames, and check which bar is long. For UI time, read the BUILD, LAYOUT and PAINT events, and turn on Track Layouts and Track Paints to name the costly render objects.

solid answer

~50 s

Run in **profile mode** on a physical, preferably slow, device and record the transition into the editor in the DevTools performance view. Select the red frames. The **Frame analysis** tab lists detected expensive operations, and the bars show whether UI or raster is over budget. For a long **UI** bar, open **Timeline events**: each frame has `BUILD`, `LAYOUT` and `PAINT` phases, and whichever is wide is the phase to chase. Turn on **Track Layouts** and **Track Paints** (these set `debugProfileLayoutsEnabled` and `debugProfilePaintsEnabled`) to get one event per render object, named after its type. That shows, for example, a `RenderImage` or a custom painter dominating paint. The docs warn these options inflate frame times, so read which objects appear, not exact durations. For a long **raster** bar, look at what the first frames draw: large images and filter or blur layers. Fix one cause, then record again.

go deeper

for a junior

Know to record the problem in profile mode on a real device and to look for the red frames in the DevTools chart.

for a middle

Explain what BUILD, LAYOUT and PAINT events mean, and what Track Layouts and Track Paints add to the timeline.

for a senior

Run the whole loop: separate UI from raster cost, use tracing to name the responsible render objects, fix one cause, and re-measure without tracing.

for a principal

Make first-open performance of heavy screens a tracked metric, with a reference device and a before-and-after trace for each fix.

## The symptom A photo-editor screen is a classic place for first-second jank. The route transition animates while the screen builds its whole tree for the first time, loads a large photo, sets up filter previews and maybe decodes thumbnails. Each of those can land on either thread, so guessing is expensive. The timeline tells you which one it is. ## Step 1: record it properly 1. Build in **profile mode** and run on a **physical device**, ideally the slowest one you support. Debug mode's JIT and asserts make the UI thread look slower than release; emulators have different GPUs. 2. Open DevTools' **performance view**, clear it, open the editor a few times, and stop. 3. In the **Flutter frames** chart, find the red frames that cluster right after navigation. ## Step 2: which thread? Compare each red frame's two bars against the display's budget, about 16 ms at 60 Hz and 8 ms at 120 Hz. - **UI over budget.** Dart work in that frame was too long. Continue with step 3. - **Raster over budget.** The scene was too expensive to draw. Continue with step 4. - **Frames marked darker red** are shader compilation on first use. That cause has its own remedies. The **Frame analysis** tab also lists hints for the selected frame, such as expensive operations the tool detected. Read those before digging further. ## Step 3: a long UI bar, read the timeline The **Timeline events** tab shows the framework's per-frame phases as nested events, including `Animate`, `BUILD`, `LAYOUT` and `PAINT`, plus `FINALIZE TREE`. Their widths tell you where the time went: | Wide event | Usually means | Next move | |---|---|---| | `BUILD` | a large tree built at once, or expensive `build` methods | Track Widget Builds, then narrow what builds | | `LAYOUT` | deep or intrinsic-heavy layout, many children laid out at once | turn on **Track Layouts** | | `PAINT` | expensive paint code, often a custom painter or many pictures | turn on **Track Paints** | | none of these, but the bar is long | your own synchronous code in that frame, such as filtering pixels in Dart | the CPU profiler; move the work off the UI isolate | **Track Layouts** and **Track Paints** live under DevTools' enhance-tracing options. They flip `debugProfileLayoutsEnabled` and `debugProfilePaintsEnabled` through service extensions, which then add a timeline event for every render object laid out or painted, named after its runtime type. A trace might show one custom painter's `paint` taking most of the frame, or a filter-preview strip laying out 40 thumbnails in one frame. The docs warn that frame times may suffer while these options are on, and the flags' own docs say the timings are not representative. Use them to learn **which** objects do work and **how many**. Judge the fix by frame times recorded with the options **off**. ## Step 4: a long raster bar Raster time comes from what the scene asks the engine to draw. For a photo editor, check the usual suspects: the full-resolution photo drawn into a small preview, blur or colour-filter layers over large areas, and translucent overlays that force offscreen layers. Each has its own fix; the frame chart only tells you that this is the thread to work on. ## Step 5: fix one thing, measure again Change one cause at a time, then re-record the same interaction with tracing options off, and compare the red frames and bar heights. Typical results for this screen: pixel work moved off the UI isolate, previews decoded at display size, heavy panels built after the transition finishes, and blur limited to a small region. ## Traps - Profiling the **second** open only. Caches are warm by then, and the first-open jank you are hunting has disappeared. - Reading durations from a trace recorded with Track Layouts or Paints on. - Treating one red frame during a route push as a regression without checking whether it existed before.

  • Why must you re-measure with Track Layouts and Track Paints turned off?
    Those options add a timeline event for every render object laid out or painted, and the flags' docs say that overhead is significant compared with the work itself. They show where work happens, but the frame times they produce are inflated. The before-and-after comparison has to come from traces recorded without them.
  • The long UI bar shows no wide BUILD, LAYOUT or PAINT event. What is left?
    Dart code that runs in the frame outside those phases, typically synchronous work in a callback, such as applying a filter to pixel data or parsing a large file when the screen opens. The CPU profiler in DevTools shows which functions took the time; the usual fix is to move that work off the UI isolate.

saying these in an interview costs you the question

  • Profile the jank in debug mode because it shows more detail
  • Durations recorded with Track Paints on are the real paint costs
  • Every red frame after a navigation is shader compilation
  • A long raster bar is fixed by optimising build methods
  • Measure only the second time the screen opens, once caches are warm