Add platform check to FocusManager app lifecycle listener (#144718)

This PR implements a temporary fix for the mobile device keyboard bug reported in [this comment](https://github.com/flutter/flutter/pull/142930#issuecomment-1981750069).

CC @gspencergoog
This commit is contained in:
Nate 2024-03-12 09:43:06 -06:00 committed by GitHub
parent 1ca88730a0
commit 61812ca3eb
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 51 additions and 6 deletions

View file

@ -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.

View file

@ -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<void> 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<void> 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);