@@ -22,16 +22,38 @@ import 'framework.dart';
2222/// be logged.
2323bool debugFocusChanges = false ;
2424
25- bool _focusDebug (String message, [Iterable <String >? details]) {
26- if (debugFocusChanges) {
27- debugPrint ('FOCUS: $message ' );
28- if (details != null && details.isNotEmpty) {
29- for (final String detail in details) {
30- debugPrint (' $detail ' );
31- }
25+ // When using _focusDebug, always call it like so:
26+ //
27+ // assert(_focusDebug(() => 'Blah $foo'));
28+ //
29+ // It needs to be inside the assert in order to be removed in release mode, and
30+ // it needs to use a closure to generate the string in order to avoid string
31+ // interpolation when debugFocusChanges is false.
32+ //
33+ // It will throw a StateError if you try to call it when the app is in release
34+ // mode.
35+ bool _focusDebug (
36+ String Function () messageFunc, [
37+ Iterable <Object > Function ()? detailsFunc,
38+ ]) {
39+ if (kReleaseMode) {
40+ throw StateError (
41+ '_focusDebug was called in Release mode. It should always be wrapped in '
42+ 'an assert. Always call _focusDebug like so:\n '
43+ r" assert(_focusDebug(() => 'Blah $foo'));"
44+ );
45+ }
46+ if (! debugFocusChanges) {
47+ return true ;
48+ }
49+ debugPrint ('FOCUS: ${messageFunc ()}' );
50+ final Iterable <Object > details = detailsFunc? .call () ?? const < Object > [];
51+ if (details.isNotEmpty) {
52+ for (final Object detail in details) {
53+ debugPrint (' $detail ' );
3254 }
3355 }
34- // Return true so that it can be easily used inside of an assert.
56+ // Return true so that it can be used inside of an assert.
3557 return true ;
3658}
3759
@@ -118,10 +140,10 @@ class _Autofocus {
118140 && scope.focusedChild == null
119141 && autofocusNode.ancestors.contains (scope);
120142 if (shouldApply) {
121- assert (_focusDebug ('Applying autofocus: $autofocusNode ' ));
143+ assert (_focusDebug (() => 'Applying autofocus: $autofocusNode ' ));
122144 autofocusNode._doRequestFocus (findFirstFocus: true );
123145 } else {
124- assert (_focusDebug ('Autofocus request discarded for node: $autofocusNode .' ));
146+ assert (_focusDebug (() => 'Autofocus request discarded for node: $autofocusNode .' ));
125147 }
126148 }
127149}
@@ -169,7 +191,7 @@ class FocusAttachment {
169191 ///
170192 /// Calling [FocusNode.dispose] will also automatically detach the node.
171193 void detach () {
172- assert (_focusDebug ('Detaching node:' , < String > [_node. toString () , 'With enclosing scope ${_node .enclosingScope }' ]));
194+ assert (_focusDebug (() => 'Detaching node:' , () => < Object > [_node, 'With enclosing scope ${_node .enclosingScope }' ]));
173195 if (isAttached) {
174196 if (_node.hasPrimaryFocus || (_node._manager != null && _node._manager! ._markedForFocus == _node)) {
175197 _node.unfocus (disposition: UnfocusDisposition .previouslyFocusedChild);
@@ -877,7 +899,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
877899 scope._doRequestFocus (findFirstFocus: true );
878900 break ;
879901 }
880- assert (_focusDebug ('Unfocused node:' , < String > ['primary focus was $this ' , 'next focus will be ${_manager ?._markedForFocus }' ]));
902+ assert (_focusDebug (() => 'Unfocused node:' , () => < Object > ['primary focus was $this ' , 'next focus will be ${_manager ?._markedForFocus }' ]));
881903 }
882904
883905 /// Removes the keyboard token from this focus node if it has one.
@@ -1063,7 +1085,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
10631085 // Note that this is overridden in FocusScopeNode.
10641086 void _doRequestFocus ({required bool findFirstFocus}) {
10651087 if (! canRequestFocus) {
1066- assert (_focusDebug ('Node NOT requesting focus because canRequestFocus is false: $this ' ));
1088+ assert (_focusDebug (() => 'Node NOT requesting focus because canRequestFocus is false: $this ' ));
10671089 return ;
10681090 }
10691091 // If the node isn't part of the tree, then we just defer the focus request
@@ -1078,7 +1100,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
10781100 return ;
10791101 }
10801102 _hasKeyboardToken = true ;
1081- assert (_focusDebug ('Node requesting focus: $this ' ));
1103+ assert (_focusDebug (() => 'Node requesting focus: $this ' ));
10821104 _markNextFocus (this );
10831105 }
10841106
@@ -1109,7 +1131,7 @@ class FocusNode with DiagnosticableTreeMixin, ChangeNotifier {
11091131 FocusNode scopeFocus = this ;
11101132 for (final FocusScopeNode ancestor in ancestors.whereType <FocusScopeNode >()) {
11111133 assert (scopeFocus != ancestor, 'Somehow made a loop by setting focusedChild to its scope.' );
1112- assert (_focusDebug ('Setting $scopeFocus as focused child for scope:' , < String > [ancestor. toString () ]));
1134+ assert (_focusDebug (() => 'Setting $scopeFocus as focused child for scope:' , () => < Object > [ancestor]));
11131135 // Remove it anywhere in the focused child history.
11141136 ancestor._focusedChildren.remove (scopeFocus);
11151137 // Add it to the end of the list, which is also the top of the queue: The
@@ -1276,7 +1298,7 @@ class FocusScopeNode extends FocusNode {
12761298 /// tree, the given scope must be a descendant of this scope.
12771299 void setFirstFocus (FocusScopeNode scope) {
12781300 assert (scope != this , 'Unexpected self-reference in setFirstFocus.' );
1279- assert (_focusDebug ('Setting scope as first focus in $this to node:' , < String > [scope. toString () ]));
1301+ assert (_focusDebug (() => 'Setting scope as first focus in $this to node:' , () => < Object > [scope]));
12801302 if (scope._parent == null ) {
12811303 _reparent (scope);
12821304 }
@@ -1306,7 +1328,7 @@ class FocusScopeNode extends FocusNode {
13061328 }
13071329
13081330 assert (_manager != null );
1309- assert (_focusDebug ('Autofocus scheduled for $node : scope $this ' ));
1331+ assert (_focusDebug (() => 'Autofocus scheduled for $node : scope $this ' ));
13101332 _manager? ._pendingAutofocuses.add (_Autofocus (scope: this , autofocusNode: node));
13111333 _manager? ._markNeedsUpdate ();
13121334 }
@@ -1542,7 +1564,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
15421564 void _markDetached (FocusNode node) {
15431565 // The node has been removed from the tree, so it no longer needs to be
15441566 // notified of changes.
1545- assert (_focusDebug ('Node was detached: $node ' ));
1567+ assert (_focusDebug (() => 'Node was detached: $node ' ));
15461568 if (_primaryFocus == node) {
15471569 _primaryFocus = null ;
15481570 }
@@ -1551,7 +1573,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
15511573
15521574 void _markPropertiesChanged (FocusNode node) {
15531575 _markNeedsUpdate ();
1554- assert (_focusDebug ('Properties changed for node $node .' ));
1576+ assert (_focusDebug (() => 'Properties changed for node $node .' ));
15551577 _dirtyNodes.add (node);
15561578 }
15571579
@@ -1575,7 +1597,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
15751597 // Request that an update be scheduled, optionally requesting focus for the
15761598 // given newFocus node.
15771599 void _markNeedsUpdate () {
1578- assert (_focusDebug ('Scheduling update, current focus is $_primaryFocus , next focus will be $_markedForFocus ' ));
1600+ assert (_focusDebug (() => 'Scheduling update, current focus is $_primaryFocus , next focus will be $_markedForFocus ' ));
15791601 if (_haveScheduledUpdate) {
15801602 return ;
15811603 }
@@ -1597,7 +1619,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
15971619 // then revert to the root scope.
15981620 _markedForFocus = rootScope;
15991621 }
1600- assert (_focusDebug ('Refreshing focus state. Next focus will be $_markedForFocus ' ));
1622+ assert (_focusDebug (() => 'Refreshing focus state. Next focus will be $_markedForFocus ' ));
16011623 // A node has requested to be the next focus, and isn't already the primary
16021624 // focus.
16031625 if (_markedForFocus != null && _markedForFocus != _primaryFocus) {
@@ -1613,7 +1635,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
16131635 }
16141636 assert (_markedForFocus == null );
16151637 if (previousFocus != _primaryFocus) {
1616- assert (_focusDebug ('Updating focus from $previousFocus to $_primaryFocus ' ));
1638+ assert (_focusDebug (() => 'Updating focus from $previousFocus to $_primaryFocus ' ));
16171639 if (previousFocus != null ) {
16181640 _dirtyNodes.add (previousFocus);
16191641 }
@@ -1624,7 +1646,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier {
16241646 for (final FocusNode node in _dirtyNodes) {
16251647 node._notify ();
16261648 }
1627- assert (_focusDebug ('Notified ${_dirtyNodes .length } dirty nodes:' , _dirtyNodes. toList (). map < String >(( FocusNode node) => node. toString ()) ));
1649+ assert (_focusDebug (() => 'Notified ${_dirtyNodes .length } dirty nodes:' , () => _dirtyNodes ));
16281650 _dirtyNodes.clear ();
16291651 if (previousFocus != _primaryFocus) {
16301652 notifyListeners ();
@@ -1766,9 +1788,9 @@ class _HighlightModeManager {
17661788 _lastInteractionWasTouch = false ;
17671789 updateMode ();
17681790
1769- assert (_focusDebug ('Received key event $message ' ));
1791+ assert (_focusDebug (() => 'Received key event $message ' ));
17701792 if (FocusManager .instance.primaryFocus == null ) {
1771- assert (_focusDebug ('No primary focus for key event, ignored: $message ' ));
1793+ assert (_focusDebug (() => 'No primary focus for key event, ignored: $message ' ));
17721794 return false ;
17731795 }
17741796
@@ -1794,11 +1816,11 @@ class _HighlightModeManager {
17941816 case KeyEventResult .ignored:
17951817 continue ;
17961818 case KeyEventResult .handled:
1797- assert (_focusDebug ('Node $node handled key event $message .' ));
1819+ assert (_focusDebug (() => 'Node $node handled key event $message .' ));
17981820 handled = true ;
17991821 break ;
18001822 case KeyEventResult .skipRemainingHandlers:
1801- assert (_focusDebug ('Node $node stopped key event propagation: $message .' ));
1823+ assert (_focusDebug (() => 'Node $node stopped key event propagation: $message .' ));
18021824 handled = false ;
18031825 break ;
18041826 }
@@ -1808,7 +1830,7 @@ class _HighlightModeManager {
18081830 break ;
18091831 }
18101832 if (! handled) {
1811- assert (_focusDebug ('Key event not handled by anyone: $message .' ));
1833+ assert (_focusDebug (() => 'Key event not handled by anyone: $message .' ));
18121834 }
18131835 return handled;
18141836 }
0 commit comments