diff --git a/packages/flutter/lib/src/widgets/focus_manager.dart b/packages/flutter/lib/src/widgets/focus_manager.dart index 54d89ff2316..61413b68a06 100644 --- a/packages/flutter/lib/src/widgets/focus_manager.dart +++ b/packages/flutter/lib/src/widgets/focus_manager.dart @@ -1677,8 +1677,16 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { if (kFlutterMemoryAllocationsEnabled) { ChangeNotifier.maybeDispatchObjectCreation(this); } - _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange); - WidgetsBinding.instance.addObserver(_appLifecycleListener); + 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 + _appLifecycleListener = _AppLifecycleListener(_appLifecycleChange); + WidgetsBinding.instance.addObserver(_appLifecycleListener!); + } rootScope._manager = this; } @@ -1695,7 +1703,9 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { @override void dispose() { - WidgetsBinding.instance.removeObserver(_appLifecycleListener); + if (_appLifecycleListener != null) { + WidgetsBinding.instance.removeObserver(_appLifecycleListener!); + } _highlightManager.dispose(); rootScope.dispose(); super.dispose(); @@ -1856,7 +1866,7 @@ class FocusManager with DiagnosticableTreeMixin, ChangeNotifier { // Allows FocusManager to respond to app lifecycle state changes, // temporarily suspending the primaryFocus when the app is inactive. - late final _AppLifecycleListener _appLifecycleListener; + _AppLifecycleListener? _appLifecycleListener; // Stores the node that was focused before the app lifecycle changed. // Will be restored as the primary focus once app is resumed. diff --git a/packages/flutter/test/widgets/focus_manager_test.dart b/packages/flutter/test/widgets/focus_manager_test.dart index 53204361233..0d1477d11d5 100644 --- a/packages/flutter/test/widgets/focus_manager_test.dart +++ b/packages/flutter/test/widgets/focus_manager_test.dart @@ -354,7 +354,44 @@ 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; + } + + 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); + + await setAppLifecycleState(AppLifecycleState.paused); + expect(focusNode.hasPrimaryFocus, isTrue); + + await setAppLifecycleState(AppLifecycleState.resumed); + expect(focusNode.hasPrimaryFocus, isTrue); + }); + 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 @@ -402,8 +439,6 @@ void main() { expect(focusNode.hasPrimaryFocus, isTrue); await setAppLifecycleState(AppLifecycleState.paused); - expect(focusNode.hasPrimaryFocus, isFalse); - focusNodeAttachment.detach(); expect(focusNode.hasPrimaryFocus, isFalse);