applyFocusChangesIfNeeded method

void applyFocusChangesIfNeeded()

Applies any pending focus changes and notifies listeners that the focus has changed.

Must not be called during the build phase. This method is meant to be called in a post-frame callback or microtask when the pending focus changes need to be resolved before something else occurs.

It can't be called during the build phase because not all listeners are safe to be called with an update during a build.

Typically, this is called automatically by the FocusManager, but sometimes it is necessary to ensure that no focus changes are pending before executing an action. For example, the MenuAnchor class uses this to make sure that the previous focus has been restored before executing a menu callback when a menu item is selected.

It is safe to call this if no focus changes are pending.

Implementation

void applyFocusChangesIfNeeded() {
  assert(
    SchedulerBinding.instance.schedulerPhase != SchedulerPhase.persistentCallbacks,
    'applyFocusChangesIfNeeded() should not be called during the build phase.'
  );

  _haveScheduledUpdate = false;
  final FocusNode? previousFocus = _primaryFocus;

  for (final _Autofocus autofocus in _pendingAutofocuses) {
    autofocus.applyIfValid(this);
  }
  _pendingAutofocuses.clear();

  if (_primaryFocus == null && _markedForFocus == null) {
    // If we don't have any current focus, and nobody has asked to focus yet,
    // then revert to the root scope.
    _markedForFocus = rootScope;
  }
  assert(_focusDebug(() => 'Refreshing focus state. Next focus will be $_markedForFocus'));
  // A node has requested to be the next focus, and isn't already the primary
  // focus.
  if (_markedForFocus != null && _markedForFocus != _primaryFocus) {
    final Set<FocusNode> previousPath = previousFocus?.ancestors.toSet() ?? <FocusNode>{};
    final Set<FocusNode> nextPath = _markedForFocus!.ancestors.toSet();
    // Notify nodes that are newly focused.
    _dirtyNodes.addAll(nextPath.difference(previousPath));
    // Notify nodes that are no longer focused
    _dirtyNodes.addAll(previousPath.difference(nextPath));

    _primaryFocus = _markedForFocus;
    _markedForFocus = null;
  }
  assert(_markedForFocus == null);
  if (previousFocus != _primaryFocus) {
    assert(_focusDebug(() => 'Updating focus from $previousFocus to $_primaryFocus'));
    if (previousFocus != null) {
      _dirtyNodes.add(previousFocus);
    }
    if (_primaryFocus != null) {
      _dirtyNodes.add(_primaryFocus!);
    }
  }
  for (final FocusNode node in _dirtyNodes) {
    node._notify();
  }
  assert(_focusDebug(() => 'Notified ${_dirtyNodes.length} dirty nodes:', () => _dirtyNodes));
  _dirtyNodes.clear();
  if (previousFocus != _primaryFocus) {
    notifyListeners();
  }
  assert(() {
    if (debugFocusChanges) {
      debugDumpFocusTree();
    }
    return true;
  }());
}