diff --git a/examples/layers/widgets/spinning_mixed.dart b/examples/layers/widgets/spinning_mixed.dart index 2b81f232db..c29e3aacd2 100644 --- a/examples/layers/widgets/spinning_mixed.dart +++ b/examples/layers/widgets/spinning_mixed.dart @@ -33,7 +33,6 @@ class Rectangle extends StatelessWidget { double? value; RenderObjectToWidgetElement? element; -BuildOwner owner = BuildOwner(); void attachWidgetTreeToRenderTree(RenderProxyBox container) { element = RenderObjectToWidgetAdapter( container: container, @@ -74,7 +73,7 @@ void attachWidgetTreeToRenderTree(RenderProxyBox container) { ), ), ), - ).attachToRenderTree(owner, element); + ).attachToRenderTree(WidgetsBinding.instance!.buildOwner!, element); } Duration? timeBase; @@ -87,7 +86,7 @@ void rotate(Duration timeStamp) { transformBox.setIdentity(); transformBox.rotateZ(delta); - owner.buildScope(element!); + WidgetsBinding.instance!.buildOwner!.buildScope(element!); } void main() { diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index bf4cc7552d..e9aeeba1a0 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -1439,12 +1439,39 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { /// This constructor is rarely called directly. To access the [FocusManager], /// consider using the [FocusManager.instance] accessor instead (which gets it /// from the [WidgetsBinding] singleton). + /// + /// This newly constructed focus manager does not have the necessary event + /// handlers registered to allow it to manage focus. To register those event + /// handlers, callers must call [registerGlobalHandlers]. See the + /// documentation in that method for caveats to watch out for. FocusManager() { rootScope._manager = this; + } + + /// Registers global input event handlers that are needed to manage focus. + /// + /// This sets the [RawKeyboard.keyEventHandler] for the shared instance of + /// [RawKeyboard] and adds a route to the global entry in the gesture routing + /// table. As such, only one [FocusManager] instance should register its + /// global handlers. + /// + /// When this focus manager is no longer needed, calling [dispose] on it will + /// unregister these handlers. + void registerGlobalHandlers() { + assert(RawKeyboard.instance.keyEventHandler == null); RawKeyboard.instance.keyEventHandler = _handleRawKeyEvent; GestureBinding.instance!.pointerRouter.addGlobalRoute(_handlePointerEvent); } + @override + void dispose() { + if (RawKeyboard.instance.keyEventHandler == _handleRawKeyEvent) { + RawKeyboard.instance.keyEventHandler = null; + GestureBinding.instance!.pointerRouter.removeGlobalRoute(_handlePointerEvent); + } + super.dispose(); + } + /// Provides convenient access to the current [FocusManager] singleton from /// the [WidgetsBinding] instance. static FocusManager get instance => WidgetsBinding.instance!.focusManager; diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 2df78ee969..fb3c823565 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -2327,7 +2327,7 @@ abstract class BuildContext { /// Size measureWidget(Widget widget) { /// final PipelineOwner pipelineOwner = PipelineOwner(); /// final MeasurementView rootView = pipelineOwner.rootNode = MeasurementView(); -/// final BuildOwner buildOwner = BuildOwner(focusManager: FailingFocusManager()); +/// final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager()); /// final RenderObjectToWidgetElement element = RenderObjectToWidgetAdapter( /// container: rootView, /// debugShortDescription: '[root]', @@ -2344,17 +2344,6 @@ abstract class BuildContext { /// } /// } /// -/// // The default FocusManager, when created, modifies some static properties -/// // that we don't want to modify, which is why we use a failing implementation -/// // here. -/// class FailingFocusManager implements FocusManager { -/// @override -/// dynamic noSuchMethod(Invocation invocation) => super.noSuchMethod(invocation); -/// -/// @override -/// String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) => 'FailingFocusManager'; -/// } -/// /// class MeasurementView extends RenderBox with RenderObjectWithChildMixin { /// @override /// void performLayout() { @@ -2370,8 +2359,14 @@ abstract class BuildContext { /// {@end-tool} class BuildOwner { /// Creates an object that manages widgets. + /// + /// If the `focusManager` argument is not specified or is null, this will + /// construct a new [FocusManager] and register its global input handlers + /// via [FocusManager.registerGlobalHandlers], which will modify static + /// state. Callers wishing to avoid altering this state can explicitly pass + /// a focus manager here. BuildOwner({ this.onBuildScheduled, FocusManager? focusManager }) : - focusManager = focusManager ?? FocusManager(); + focusManager = focusManager ?? (FocusManager()..registerGlobalHandlers()); /// Called on each build pass when the first buildable element is marked /// dirty. @@ -2402,6 +2397,12 @@ class BuildOwner { /// the [FocusScopeNode] for a given [BuildContext]. /// /// See [FocusManager] for more details. + /// + /// This field will default to a [FocusManager] that has registered its + /// global input handlers via [FocusManager.registerGlobalHandlers]. Callers + /// wishing to avoid registering those handlers (and modifying the associated + /// static state) can explicitly pass a focus manager to the [new BuildOwner] + /// constructor. FocusManager focusManager; /// Adds an element to the dirty elements list so that it will be rebuilt diff --git a/packages/flutter/test/widgets/framework_test.dart b/packages/flutter/test/widgets/framework_test.dart index 4bbb450cf3..936231e0e1 100644 --- a/packages/flutter/test/widgets/framework_test.dart +++ b/packages/flutter/test/widgets/framework_test.dart @@ -1490,7 +1490,7 @@ void main() { final int pointerRouterCount = GestureBinding.instance!.pointerRouter.debugGlobalRouteCount; final RawKeyEventHandler? rawKeyEventHandler = RawKeyboard.instance.keyEventHandler; expect(rawKeyEventHandler, isNotNull); - BuildOwner(focusManager: _FakeFocusManager()); + BuildOwner(focusManager: FocusManager()); expect(GestureBinding.instance!.pointerRouter.debugGlobalRouteCount, pointerRouterCount); expect(RawKeyboard.instance.keyEventHandler, same(rawKeyEventHandler)); }); @@ -1516,18 +1516,6 @@ void main() { }); } -class _FakeFocusManager implements FocusManager { - @override - dynamic noSuchMethod(Invocation invocation) { - return super.noSuchMethod(invocation); - } - - @override - String toString({ DiagnosticLevel minLevel = DiagnosticLevel.info }) { - return '_FakeFocusManager'; - } -} - class _WidgetWithNoVisitChildren extends StatelessWidget { const _WidgetWithNoVisitChildren(this.child, { Key? key }) : super(key: key); diff --git a/packages/flutter/test/widgets/independent_widget_layout_test.dart b/packages/flutter/test/widgets/independent_widget_layout_test.dart index 83522bfb61..2b54689592 100644 --- a/packages/flutter/test/widgets/independent_widget_layout_test.dart +++ b/packages/flutter/test/widgets/independent_widget_layout_test.dart @@ -54,7 +54,7 @@ class OffscreenWidgetTree { } final RenderView renderView = OffscreenRenderView(); - final BuildOwner buildOwner = BuildOwner(); + final BuildOwner buildOwner = BuildOwner(focusManager: FocusManager()); final PipelineOwner pipelineOwner = PipelineOwner(); RenderObjectToWidgetElement? root; diff --git a/packages/flutter_test/lib/src/binding.dart b/packages/flutter_test/lib/src/binding.dart index cafaf87b98..773a890cd8 100644 --- a/packages/flutter_test/lib/src/binding.dart +++ b/packages/flutter_test/lib/src/binding.dart @@ -897,7 +897,8 @@ abstract class TestWidgetsFlutterBinding extends BindingBase FlutterError.demangleStackTrace = _oldStackTraceDemangler; _pendingExceptionDetails = null; _parentZone = null; - buildOwner!.focusManager = FocusManager(); + buildOwner!.focusManager.dispose(); + buildOwner!.focusManager = FocusManager()..registerGlobalHandlers(); // Disabling the warning because @visibleForTesting doesn't take the testing // framework itself into account, but we don't want it visible outside of // tests.