drawFrame method

  1. @override
void drawFrame()

Pump the build and rendering pipeline to generate a frame.

This method is called by handleDrawFrame, which itself is called automatically by the engine when it is time to lay out and paint a frame.

Each frame consists of the following phases:

  1. The animation phase: The handleBeginFrame method, which is registered with PlatformDispatcher.onBeginFrame, invokes all the transient frame callbacks registered with scheduleFrameCallback, in registration order. This includes all the Ticker instances that are driving AnimationController objects, which means all of the active Animation objects tick at this point.

  2. Microtasks: After handleBeginFrame returns, any microtasks that got scheduled by transient frame callbacks get to run. This typically includes callbacks for futures from Tickers and AnimationControllers that completed this frame.

After handleBeginFrame, handleDrawFrame, which is registered with PlatformDispatcher.onDrawFrame, is called, which invokes all the persistent frame callbacks, of which the most notable is this method, drawFrame, which proceeds as follows:

  1. The build phase: All the dirty Elements in the widget tree are rebuilt (see State.build). See State.setState for further details on marking a widget dirty for building. See BuildOwner for more information on this step.

  2. The layout phase: All the dirty RenderObjects in the system are laid out (see RenderObject.performLayout). See RenderObject.markNeedsLayout for further details on marking an object dirty for layout.

  3. The compositing bits phase: The compositing bits on any dirty RenderObject objects are updated. See RenderObject.markNeedsCompositingBitsUpdate.

  4. The paint phase: All the dirty RenderObjects in the system are repainted (see RenderObject.paint). This generates the Layer tree. See RenderObject.markNeedsPaint for further details on marking an object dirty for paint.

  5. The compositing phase: The layer tree is turned into a Scene and sent to the GPU.

  6. The semantics phase: All the dirty RenderObjects in the system have their semantics updated (see RenderObject.assembleSemanticsNode). This generates the SemanticsNode tree. See RenderObject.markNeedsSemanticsUpdate for further details on marking an object dirty for semantics.

For more details on steps 4-8, see PipelineOwner.

  1. The finalization phase in the widgets layer: The widgets tree is finalized. This causes State.dispose to be invoked on any objects that were removed from the widgets tree this frame. See BuildOwner.finalizeTree for more details.

  2. The finalization phase in the scheduler layer: After drawFrame returns, handleDrawFrame then invokes post-frame callbacks (registered with addPostFrameCallback).


// When editing the above, also update rendering/binding.dart's copy.
void drawFrame() {
  assert(() {
    debugBuildingDirtyElements = true;
    return true;

  TimingsCallback? firstFrameCallback;
  bool debugFrameWasSentToEngine = false;
  if (_needToReportFirstFrame) {

    firstFrameCallback = (List<FrameTiming> timings) {
      if (!kReleaseMode) {
        // Change the current user tag back to the default tag. At this point,
        // the user tag should be set to "AppStartUp" (originally set in the
        // engine), so we need to change it back to the default tag to mark
        // the end of app start up for CPU profiles.
        developer.Timeline.instantSync('Rasterized first useful frame');
        developer.postEvent('Flutter.FirstFrame', <String, dynamic>{});
      firstFrameCallback = null;
    // Callback is only invoked when FlutterView.render is called. When
    // sendFramesToEngine is set to false during the frame, it will not be
    // called and we need to remove the callback (see below).

  try {
    if (rootElement != null) {
    assert(() {
      debugFrameWasSentToEngine = sendFramesToEngine;
      return true;
  } finally {
    assert(() {
      debugBuildingDirtyElements = false;
      return true;
  if (!kReleaseMode) {
    if (_needToReportFirstFrame && sendFramesToEngine) {
      developer.Timeline.instantSync('Widgets built first useful frame');
  _needToReportFirstFrame = false;
  if (firstFrameCallback != null && !sendFramesToEngine) {
    // This frame is deferred and not the first frame sent to the engine that
    // should be reported.
    _needToReportFirstFrame = true;