buildScope method

void buildScope (Element context, [ VoidCallback callback ])

Establishes a scope for updating the widget tree, and calls the given callback, if any. Then, builds all the elements that were marked as dirty using scheduleBuildFor, in depth order.

This mechanism prevents build methods from transitively requiring other build methods to run, potentially causing infinite loops.

The dirty list is processed after callback returns, building all the elements that were marked as dirty using scheduleBuildFor, in depth order. If elements are marked as dirty while this method is running, they must be deeper than the context node, and deeper than any previously-built node in this pass.

To flush the current dirty list without performing any other work, this function can be called with no callback. This is what the framework does each frame, in WidgetsBinding.drawFrame.

Only one buildScope can be active at a time.

A buildScope implies a lockState scope as well.

To print a console message every time this method is called, set debugPrintBuildScope to true. This is useful when debugging problems involving widgets not getting marked dirty, or getting marked dirty too often.

Implementation

void buildScope(Element context, [ VoidCallback callback ]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;
  assert(context != null);
  assert(_debugStateLockLevel >= 0);
  assert(!_debugBuilding);
  assert(() {
    if (debugPrintBuildScope)
      debugPrint('buildScope called with context $context; dirty list is: $_dirtyElements');
    _debugStateLockLevel += 1;
    _debugBuilding = true;
    return true;
  }());
  Timeline.startSync('Build', arguments: timelineWhitelistArguments);
  try {
    _scheduledFlushDirtyElements = true;
    if (callback != null) {
      assert(_debugStateLocked);
      Element debugPreviousBuildTarget;
      assert(() {
        context._debugSetAllowIgnoredCallsToMarkNeedsBuild(true);
        debugPreviousBuildTarget = _debugCurrentBuildTarget;
        _debugCurrentBuildTarget = context;
        return true;
      }());
      _dirtyElementsNeedsResorting = false;
      try {
        callback();
      } finally {
        assert(() {
          context._debugSetAllowIgnoredCallsToMarkNeedsBuild(false);
          assert(_debugCurrentBuildTarget == context);
          _debugCurrentBuildTarget = debugPreviousBuildTarget;
          _debugElementWasRebuilt(context);
          return true;
        }());
      }
    }
    _dirtyElements.sort(Element._sort);
    _dirtyElementsNeedsResorting = false;
    int dirtyCount = _dirtyElements.length;
    int index = 0;
    while (index < dirtyCount) {
      assert(_dirtyElements[index] != null);
      assert(_dirtyElements[index]._inDirtyList);
      assert(!_dirtyElements[index]._active || _dirtyElements[index]._debugIsInScope(context));
      try {
        _dirtyElements[index].rebuild();
      } catch (e, stack) {
        _debugReportException(
          ErrorDescription('while rebuilding dirty elements'),
          e,
          stack,
          informationCollector: () sync* {
            yield DiagnosticsDebugCreator(DebugCreator(_dirtyElements[index]));
            yield _dirtyElements[index].describeElement('The element being rebuilt at the time was index $index of $dirtyCount');
          },
        );
      }
      index += 1;
      if (dirtyCount < _dirtyElements.length || _dirtyElementsNeedsResorting) {
        _dirtyElements.sort(Element._sort);
        _dirtyElementsNeedsResorting = false;
        dirtyCount = _dirtyElements.length;
        while (index > 0 && _dirtyElements[index - 1].dirty) {
          // It is possible for previously dirty but inactive widgets to move right in the list.
          // We therefore have to move the index left in the list to account for this.
          // We don't know how many could have moved. However, we do know that the only possible
          // change to the list is that nodes that were previously to the left of the index have
          // now moved to be to the right of the right-most cleaned node, and we do know that
          // all the clean nodes were to the left of the index. So we move the index left
          // until just after the right-most clean node.
          index -= 1;
        }
      }
    }
    assert(() {
      if (_dirtyElements.any((Element element) => element._active && element.dirty)) {
        throw FlutterError.fromParts(<DiagnosticsNode>[
          ErrorSummary('buildScope missed some dirty elements.'),
          ErrorHint('This probably indicates that the dirty list should have been resorted but was not.'),
          Element.describeElements('The list of dirty elements at the end of the buildScope call was', _dirtyElements)
        ]);
      }
      return true;
    }());
  } finally {
    for (Element element in _dirtyElements) {
      assert(element._inDirtyList);
      element._inDirtyList = false;
    }
    _dirtyElements.clear();
    _scheduledFlushDirtyElements = false;
    _dirtyElementsNeedsResorting = null;
    Timeline.finishSync();
    assert(_debugBuilding);
    assert(() {
      _debugBuilding = false;
      _debugStateLockLevel -= 1;
      if (debugPrintBuildScope)
        debugPrint('buildScope finished');
      return true;
    }());
  }
  assert(_debugStateLockLevel >= 0);
}