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() {
|
void _handleHistoryChanged() {
|
||||||
final bool navigatorCanPop = canPop();
|
final bool navigatorCanPop = canPop();
|
||||||
late final bool routeBlocksPop;
|
final bool routeBlocksPop;
|
||||||
if (!navigatorCanPop) {
|
if (!navigatorCanPop) {
|
||||||
final _RouteEntry? lastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
|
final _RouteEntry? lastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
|
||||||
routeBlocksPop = lastEntry != null
|
routeBlocksPop = lastEntry != null
|
||||||
|
@ -1807,6 +1807,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||||||
void addScopedWillPopCallback(WillPopCallback callback) {
|
void addScopedWillPopCallback(WillPopCallback callback) {
|
||||||
assert(_scopeKey.currentState != null, 'Tried to add a willPop callback to a route that is not currently in the tree.');
|
assert(_scopeKey.currentState != null, 'Tried to add a willPop callback to a route that is not currently in the tree.');
|
||||||
_willPopCallbacks.add(callback);
|
_willPopCallbacks.add(callback);
|
||||||
|
if (_willPopCallbacks.length == 1) {
|
||||||
|
_maybeDispatchNavigationNotification();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Remove one of the callbacks run by [willPop].
|
/// 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) {
|
void removeScopedWillPopCallback(WillPopCallback callback) {
|
||||||
assert(_scopeKey.currentState != null, 'Tried to remove a willPop callback from a route that is not currently in the tree.');
|
assert(_scopeKey.currentState != null, 'Tried to remove a willPop callback from a route that is not currently in the tree.');
|
||||||
_willPopCallbacks.remove(callback);
|
_willPopCallbacks.remove(callback);
|
||||||
|
if (_willPopCallbacks.isEmpty) {
|
||||||
|
_maybeDispatchNavigationNotification();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Registers the existence of a [PopEntry] in the route.
|
/// 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
|
// canPop indicates that the originator of the Notification can handle a
|
||||||
// pop. In the case of PopScope, it handles pops when canPop is
|
// pop. In the case of PopScope, it handles pops when canPop is
|
||||||
// false. Hence the seemingly backward logic here.
|
// 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.
|
// Avoid dispatching a notification in the middle of a build.
|
||||||
switch (SchedulerBinding.instance.schedulerPhase) {
|
switch (SchedulerBinding.instance.schedulerPhase) {
|
||||||
|
@ -1971,6 +1971,78 @@ void main() {
|
|||||||
await tester.restoreFrom(restorationData);
|
await tester.restoreFrom(restorationData);
|
||||||
expect(find.byType(AlertDialog), findsOneWidget);
|
expect(find.byType(AlertDialog), findsOneWidget);
|
||||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/33615
|
}, 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) {
|
double _getOpacity(GlobalKey key, WidgetTester tester) {
|
||||||
|
Loading…
x
Reference in New Issue
Block a user