Will pop scope on home route (#152057)
Fixes a bug where WillPopScope no longer worked on the home route. With this PR, Android's predictive back feature will be explicitly disabled when a WillPopScope widget is in the widget tree. To get the same behavior and still support predictive back, use PopScope.
This commit is contained in:
parent
c9ee9ad616
commit
bb8f6b04d5
@ -3613,7 +3613,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
||||
|
||||
void _handleHistoryChanged() {
|
||||
final bool navigatorCanPop = canPop();
|
||||
late final bool routeBlocksPop;
|
||||
final bool routeBlocksPop;
|
||||
if (!navigatorCanPop) {
|
||||
final _RouteEntry? lastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
|
||||
routeBlocksPop = lastEntry != null
|
||||
|
@ -1807,6 +1807,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
void addScopedWillPopCallback(WillPopCallback callback) {
|
||||
assert(_scopeKey.currentState != null, 'Tried to add a willPop callback to a route that is not currently in the tree.');
|
||||
_willPopCallbacks.add(callback);
|
||||
if (_willPopCallbacks.length == 1) {
|
||||
_maybeDispatchNavigationNotification();
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove one of the callbacks run by [willPop].
|
||||
@ -1823,6 +1826,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
void removeScopedWillPopCallback(WillPopCallback callback) {
|
||||
assert(_scopeKey.currentState != null, 'Tried to remove a willPop callback from a route that is not currently in the tree.');
|
||||
_willPopCallbacks.remove(callback);
|
||||
if (_willPopCallbacks.isEmpty) {
|
||||
_maybeDispatchNavigationNotification();
|
||||
}
|
||||
}
|
||||
|
||||
/// Registers the existence of a [PopEntry] in the route.
|
||||
@ -1860,7 +1866,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
||||
// canPop indicates that the originator of the Notification can handle a
|
||||
// pop. In the case of PopScope, it handles pops when canPop is
|
||||
// false. Hence the seemingly backward logic here.
|
||||
canHandlePop: popDisposition == RoutePopDisposition.doNotPop,
|
||||
canHandlePop: popDisposition == RoutePopDisposition.doNotPop
|
||||
|| _willPopCallbacks.isNotEmpty,
|
||||
);
|
||||
// Avoid dispatching a notification in the middle of a build.
|
||||
switch (SchedulerBinding.instance.schedulerPhase) {
|
||||
|
@ -1971,6 +1971,78 @@ void main() {
|
||||
await tester.restoreFrom(restorationData);
|
||||
expect(find.byType(AlertDialog), findsOneWidget);
|
||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
||||
|
||||
group('NavigationNotifications', () {
|
||||
testWidgets('with no WillPopScope', (WidgetTester tester) async {
|
||||
final List<NavigationNotification> notifications = <NavigationNotification>[];
|
||||
await tester.pumpWidget(
|
||||
NotificationListener<NavigationNotification>(
|
||||
onNotification: (NavigationNotification notification) {
|
||||
notifications.add(notification);
|
||||
return true;
|
||||
},
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Navigator(
|
||||
initialRoute: '/',
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
settings: settings,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Only one notification, from the initial route, where a pop can't be
|
||||
// handled because there's no other route to pop.
|
||||
expect(notifications, hasLength(1));
|
||||
expect(notifications.first.canHandlePop, isFalse);
|
||||
});
|
||||
|
||||
testWidgets('with WillPopScope', (WidgetTester tester) async {
|
||||
final List<NavigationNotification> notifications = <NavigationNotification>[];
|
||||
await tester.pumpWidget(
|
||||
NotificationListener<NavigationNotification>(
|
||||
onNotification: (NavigationNotification notification) {
|
||||
notifications.add(notification);
|
||||
return true;
|
||||
},
|
||||
child: Directionality(
|
||||
textDirection: TextDirection.ltr,
|
||||
child: Navigator(
|
||||
initialRoute: '/',
|
||||
onGenerateRoute: (RouteSettings settings) {
|
||||
return MaterialPageRoute<void>(
|
||||
builder: (BuildContext context) {
|
||||
return WillPopScope(
|
||||
onWillPop: () {
|
||||
return Future<bool>.value(false);
|
||||
},
|
||||
child: const SizedBox.shrink(),
|
||||
);
|
||||
},
|
||||
settings: settings,
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// Two notifications. The first is from the initial route, where a pop
|
||||
// can't be handled because it's the only route. The second is from
|
||||
// registering the WillPopScope, where it will always want to receive
|
||||
// pops.
|
||||
expect(notifications, hasLength(2));
|
||||
expect(notifications.first.canHandlePop, isFalse);
|
||||
expect(notifications.last.canHandlePop, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
double _getOpacity(GlobalKey key, WidgetTester tester) {
|
||||
|
Loading…
x
Reference in New Issue
Block a user