diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index f27293118f3..44f49601b00 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -1532,19 +1532,29 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { if (kFlutterMemoryAllocationsEnabled) { ChangeNotifier.maybeDispatchObjectCreation(this); } - if (kIsWeb || defaultTargetPlatform != TargetPlatform.android) { - // It appears that some Android keyboard implementations can cause - // app lifecycle state changes: adding this listener would cause the - // text field to unfocus as the user is trying to type. - // - // Until this is resolved, we won't be adding the listener to Android apps. - // https://github.com/flutter/flutter/pull/142930#issuecomment-1981750069 + if (_respondToWindowFocus) { _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange); WidgetsBinding.instance.addObserver(_appLifecycleListener!); } rootScope._manager = this; } + /// It appears that some Android keyboard implementations can cause + /// app lifecycle state changes: adding the app lifecycle listener would + /// cause the text field to unfocus as the user is trying to type. + /// + /// Additionally, on iOS, input fields aren't automatically populated + /// with relevant data when using autofill. + /// + /// Until these are resolved, we won't be adding the listener to mobile platforms. + /// https://github.com/flutter/flutter/issues/148475#issuecomment-2118407411 + /// https://github.com/flutter/flutter/pull/142930#issuecomment-1981750069 + bool get _respondToWindowFocus => kIsWeb || switch (defaultTargetPlatform) { + TargetPlatform.android || TargetPlatform.iOS => false, + TargetPlatform.fuchsia || TargetPlatform.linux => true, + TargetPlatform.windows || TargetPlatform.macOS => true, + }; + /// Registers global input event handlers that are needed to manage focus. /// /// This calls the [HardwareKeyboard.addHandler] on the shared instance of @@ -1875,7 +1885,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { /// Enables this [FocusManager] to listen to changes of the application /// lifecycle if it does not already have an application lifecycle listener - /// active, and the current platform is detected as [kIsWeb] or non-Android. + /// active, and the app isn't running on a native mobile platform. /// /// Typically, the application lifecycle listener for this [FocusManager] is /// setup at construction, but sometimes it is necessary to manually initialize @@ -1889,13 +1899,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { /// supported. @visibleForTesting void listenToApplicationLifecycleChangesIfSupported() { - if (_appLifecycleListener == null && (kIsWeb || defaultTargetPlatform != TargetPlatform.android)) { - // It appears that some Android keyboard implementations can cause - // app lifecycle state changes: adding this listener would cause the - // text field to unfocus as the user is trying to type. - // - // Until this is resolved, we won't be adding the listener to Android apps. - // https://github.com/flutter/flutter/pull/142930#issuecomment-1981750069 + if (_appLifecycleListener == null && _respondToWindowFocus) { _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange); WidgetsBinding.instance.addObserver(_appLifecycleListener!); } diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 09cf1800841..76c4a802503 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -354,44 +354,39 @@ void main() { logs.clear(); }, variant: KeySimulatorTransitModeVariant.all()); - testWidgets('FocusManager ignores app lifecycle changes on Android.', (WidgetTester tester) async { - final bool shouldRespond = kIsWeb || defaultTargetPlatform != TargetPlatform.android; - if (shouldRespond) { - return; - } + testWidgets( + 'FocusManager ignores app lifecycle changes on Android and iOS.', + (WidgetTester tester) async { + Future setAppLifecycleState(AppLifecycleState state) async { + final ByteData? message = const StringCodec().encodeMessage(state.toString()); + await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger + .handlePlatformMessage('flutter/lifecycle', message, (_) {}); + } - Future setAppLifecycleState(AppLifecycleState state) async { - final ByteData? message = const StringCodec().encodeMessage(state.toString()); - await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger - .handlePlatformMessage('flutter/lifecycle', message, (_) {}); - } + final BuildContext context = await setupWidget(tester); + final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope'); + addTearDown(scope.dispose); + final FocusAttachment scopeAttachment = scope.attach(context); + final FocusNode focusNode = FocusNode(debugLabel: 'Focus Node'); + addTearDown(focusNode.dispose); + final FocusAttachment focusNodeAttachment = focusNode.attach(context); + scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); + focusNodeAttachment.reparent(parent: scope); + focusNode.requestFocus(); + await tester.pump(); + expect(focusNode.hasPrimaryFocus, isTrue); - final BuildContext context = await setupWidget(tester); - final FocusScopeNode scope = FocusScopeNode(debugLabel: 'Scope'); - addTearDown(scope.dispose); - final FocusAttachment scopeAttachment = scope.attach(context); - final FocusNode focusNode = FocusNode(debugLabel: 'Focus Node'); - addTearDown(focusNode.dispose); - final FocusAttachment focusNodeAttachment = focusNode.attach(context); - scopeAttachment.reparent(parent: tester.binding.focusManager.rootScope); - focusNodeAttachment.reparent(parent: scope); - focusNode.requestFocus(); - await tester.pump(); - expect(focusNode.hasPrimaryFocus, isTrue); + await setAppLifecycleState(AppLifecycleState.paused); + expect(focusNode.hasPrimaryFocus, isTrue); - await setAppLifecycleState(AppLifecycleState.paused); - expect(focusNode.hasPrimaryFocus, isTrue); - - await setAppLifecycleState(AppLifecycleState.resumed); - expect(focusNode.hasPrimaryFocus, isTrue); - }); + await setAppLifecycleState(AppLifecycleState.resumed); + expect(focusNode.hasPrimaryFocus, isTrue); + }, + skip: kIsWeb, // [intended] + variant: const TargetPlatformVariant({TargetPlatform.android, TargetPlatform.iOS}), + ); testWidgets('FocusManager responds to app lifecycle changes.', (WidgetTester tester) async { - final bool shouldRespond = kIsWeb || defaultTargetPlatform != TargetPlatform.android; - if (!shouldRespond) { - return; - } - Future setAppLifecycleState(AppLifecycleState state) async { final ByteData? message = const StringCodec().encodeMessage(state.toString()); await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger