Unverified Commit dfd4147c authored by Justin McCandless's avatar Justin McCandless Committed by GitHub

Fix stuck predictive back platform channel calls (#133368)

Fix a Google test flakiness increase.
parent 69f61a28
...@@ -1346,13 +1346,19 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver { ...@@ -1346,13 +1346,19 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
/// the platform with [NavigationNotification.canHandlePop] and stops /// the platform with [NavigationNotification.canHandlePop] and stops
/// bubbling. /// bubbling.
bool _defaultOnNavigationNotification(NavigationNotification notification) { bool _defaultOnNavigationNotification(NavigationNotification notification) {
// Don't do anything with navigation notifications if there is no engine switch (_appLifecycleState) {
// attached. case null:
if (_appLifecycleState != AppLifecycleState.detached) { case AppLifecycleState.detached:
case AppLifecycleState.inactive:
// Avoid updating the engine when the app isn't ready.
return true;
case AppLifecycleState.resumed:
case AppLifecycleState.hidden:
case AppLifecycleState.paused:
SystemNavigator.setFrameworkHandlesBack(notification.canHandlePop); SystemNavigator.setFrameworkHandlesBack(notification.canHandlePop);
}
return true; return true;
} }
}
@override @override
void didChangeAppLifecycleState(AppLifecycleState state) { void didChangeAppLifecycleState(AppLifecycleState state) {
...@@ -1366,6 +1372,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver { ...@@ -1366,6 +1372,7 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
_updateRouting(); _updateRouting();
_locale = _resolveLocales(WidgetsBinding.instance.platformDispatcher.locales, widget.supportedLocales); _locale = _resolveLocales(WidgetsBinding.instance.platformDispatcher.locales, widget.supportedLocales);
WidgetsBinding.instance.addObserver(this); WidgetsBinding.instance.addObserver(this);
_appLifecycleState = WidgetsBinding.instance.lifecycleState;
} }
@override @override
......
...@@ -1219,11 +1219,7 @@ void main() { ...@@ -1219,11 +1219,7 @@ void main() {
group('Android Predictive Back', () { group('Android Predictive Back', () {
bool? lastFrameworkHandlesBack; bool? lastFrameworkHandlesBack;
setUp(() { setUp(() async {
// Initialize to false. Because this uses a static boolean internally, it
// is not reset between tests or calls to pumpWidget. Explicitly setting
// it to false before each test makes them behave deterministically.
SystemNavigator.setFrameworkHandlesBack(false);
lastFrameworkHandlesBack = null; lastFrameworkHandlesBack = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
...@@ -1233,12 +1229,17 @@ void main() { ...@@ -1233,12 +1229,17 @@ void main() {
} }
return; return;
}); });
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
(ByteData? data) {},
);
}); });
tearDown(() { tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null); .setMockMethodCallHandler(SystemChannels.platform, null);
SystemNavigator.setFrameworkHandlesBack(true);
}); });
testWidgets('System back navigation inside of tabs', (WidgetTester tester) async { testWidgets('System back navigation inside of tabs', (WidgetTester tester) async {
......
...@@ -683,6 +683,90 @@ void main() { ...@@ -683,6 +683,90 @@ void main() {
expect(copySpy.invoked, isTrue); expect(copySpy.invoked, isTrue);
expect(pasteSpy.invoked, isTrue); expect(pasteSpy.invoked, isTrue);
}, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS })); }, variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.iOS, TargetPlatform.macOS }));
group('Android Predictive Back', () {
Future<void> setAppLifeCycleState(AppLifecycleState state) async {
final ByteData? message = const StringCodec().encodeMessage(state.toString());
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage('flutter/lifecycle', message, (ByteData? data) {});
}
final List<bool> frameworkHandlesBacks = <bool>[];
setUp(() async {
frameworkHandlesBacks.clear();
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
if (methodCall.method == 'SystemNavigator.setFrameworkHandlesBack') {
expect(methodCall.arguments, isA<bool>());
frameworkHandlesBacks.add(methodCall.arguments as bool);
}
return;
});
});
tearDown(() async {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null);
await setAppLifeCycleState(AppLifecycleState.resumed);
});
testWidgets('WidgetsApp calls setFrameworkHandlesBack only when app is ready', (WidgetTester tester) async {
// Start in the `resumed` state, where setFrameworkHandlesBack should be
// called like normal.
await setAppLifeCycleState(AppLifecycleState.resumed);
late BuildContext currentContext;
await tester.pumpWidget(
WidgetsApp(
color: const Color(0xFF123456),
builder: (BuildContext context, Widget? child) {
currentContext = context;
return const Placeholder();
},
),
);
expect(frameworkHandlesBacks, isEmpty);
const NavigationNotification(canHandlePop: true).dispatch(currentContext);
await tester.pumpAndSettle();
expect(frameworkHandlesBacks, isNotEmpty);
expect(frameworkHandlesBacks.last, isTrue);
const NavigationNotification(canHandlePop: false).dispatch(currentContext);
await tester.pumpAndSettle();
expect(frameworkHandlesBacks.last, isFalse);
// Set the app state to inactive, where setFrameworkHandlesBack shouldn't
// be called.
await setAppLifeCycleState(AppLifecycleState.inactive);
final int finalCallsLength = frameworkHandlesBacks.length;
const NavigationNotification(canHandlePop: true).dispatch(currentContext);
await tester.pumpAndSettle();
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
const NavigationNotification(canHandlePop: false).dispatch(currentContext);
await tester.pumpAndSettle();
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
// Set the app state to detached, which also shouldn't call
// setFrameworkHandlesBack. Must go to paused, then detached.
await setAppLifeCycleState(AppLifecycleState.paused);
await setAppLifeCycleState(AppLifecycleState.detached);
const NavigationNotification(canHandlePop: true).dispatch(currentContext);
await tester.pumpAndSettle();
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
const NavigationNotification(canHandlePop: false).dispatch(currentContext);
await tester.pumpAndSettle();
expect(frameworkHandlesBacks, hasLength(finalCallsLength));
},
skip: kIsWeb, // [intended] predictive back is only native Android.
variant: const TargetPlatformVariant(<TargetPlatform>{ TargetPlatform.android })
);
});
} }
typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation); typedef SimpleRouterDelegateBuilder = Widget Function(BuildContext, RouteInformation);
......
...@@ -4159,11 +4159,7 @@ void main() { ...@@ -4159,11 +4159,7 @@ void main() {
group('Android Predictive Back', () { group('Android Predictive Back', () {
bool? lastFrameworkHandlesBack; bool? lastFrameworkHandlesBack;
setUp(() { setUp(() async {
// Initialize to false. Because this uses a static boolean internally, it
// is not reset between tests or calls to pumpWidget. Explicitly setting
// it to false before each test makes them behave deterministically.
SystemNavigator.setFrameworkHandlesBack(false);
lastFrameworkHandlesBack = null; lastFrameworkHandlesBack = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
...@@ -4173,12 +4169,17 @@ void main() { ...@@ -4173,12 +4169,17 @@ void main() {
} }
return; return;
}); });
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
(ByteData? data) {},
);
}); });
tearDown(() { tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null); .setMockMethodCallHandler(SystemChannels.platform, null);
SystemNavigator.setFrameworkHandlesBack(true);
}); });
testWidgets('a single route is already defaulted to false', (WidgetTester tester) async { testWidgets('a single route is already defaulted to false', (WidgetTester tester) async {
......
...@@ -11,11 +11,7 @@ import 'navigator_utils.dart'; ...@@ -11,11 +11,7 @@ import 'navigator_utils.dart';
void main() { void main() {
bool? lastFrameworkHandlesBack; bool? lastFrameworkHandlesBack;
setUp(() { setUp(() async {
// Initialize to false. Because this uses a static boolean internally, it
// is not reset between tests or calls to pumpWidget. Explicitly setting
// it to false before each test makes them behave deterministically.
SystemNavigator.setFrameworkHandlesBack(false);
lastFrameworkHandlesBack = null; lastFrameworkHandlesBack = null;
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async { .setMockMethodCallHandler(SystemChannels.platform, (MethodCall methodCall) async {
...@@ -25,12 +21,17 @@ void main() { ...@@ -25,12 +21,17 @@ void main() {
} }
return; return;
}); });
await TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.handlePlatformMessage(
'flutter/lifecycle',
const StringCodec().encodeMessage(AppLifecycleState.resumed.toString()),
(ByteData? data) {},
);
}); });
tearDown(() { tearDown(() {
TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger TestDefaultBinaryMessengerBinding.instance.defaultBinaryMessenger
.setMockMethodCallHandler(SystemChannels.platform, null); .setMockMethodCallHandler(SystemChannels.platform, null);
SystemNavigator.setFrameworkHandlesBack(true);
}); });
testWidgets('toggling canPop on root route allows/prevents backs', (WidgetTester tester) async { testWidgets('toggling canPop on root route allows/prevents backs', (WidgetTester tester) async {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment