mirror of
https://github.com/flutter/flutter
synced 2024-10-01 14:04:10 +00:00
Refactors page API (#137792)
fixes https://github.com/flutter/flutter/issues/137458 Chagnes: 1. Navigator.pop will always pop page based route 2. add a onDidRemovePage callback to replace onPopPage 3. Page.canPop and Page.onPopInvoked mirrors the PopScope, but in Page class. migration guide https://github.com/flutter/website/pull/10523
This commit is contained in:
parent
27f683d6c3
commit
a36ff80cf6
|
@ -168,14 +168,10 @@ class _BottomNavTabState extends State<_BottomNavTab> {
|
|||
},
|
||||
child: Navigator(
|
||||
key: _navigatorKey,
|
||||
onPopPage: (Route<void> route, void result) {
|
||||
if (!route.didPop(null)) {
|
||||
return false;
|
||||
}
|
||||
onDidRemovePage: (Page<Object?> page) {
|
||||
widget.onChangedPages(<_TabPage>[
|
||||
...widget.pages,
|
||||
]..removeLast());
|
||||
return true;
|
||||
},
|
||||
pages: widget.pages.map((_TabPage page) {
|
||||
switch (page) {
|
||||
|
|
160
examples/api/lib/widgets/page/page_can_pop.0.dart
Normal file
160
examples/api/lib/widgets/page/page_can_pop.0.dart
Normal file
|
@ -0,0 +1,160 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
// This sample demonstrates showing a confirmation dialog before navigating
|
||||
// away from a page.
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
void main() => runApp(const PageApiExampleApp());
|
||||
|
||||
class PageApiExampleApp extends StatefulWidget {
|
||||
const PageApiExampleApp({super.key});
|
||||
|
||||
@override
|
||||
State<PageApiExampleApp> createState() => _PageApiExampleAppState();
|
||||
}
|
||||
|
||||
class _PageApiExampleAppState extends State<PageApiExampleApp> {
|
||||
final RouterDelegate<Object> delegate = MyRouterDelegate();
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return MaterialApp.router(
|
||||
routerDelegate: delegate,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class MyRouterDelegate extends RouterDelegate<Object> with PopNavigatorRouterDelegateMixin<Object>, ChangeNotifier {
|
||||
// This example doesn't use RouteInformationProvider.
|
||||
@override
|
||||
Future<void> setNewRoutePath(Object configuration) async => throw UnimplementedError();
|
||||
|
||||
@override
|
||||
final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();
|
||||
|
||||
static MyRouterDelegate of(BuildContext context) => Router.of(context).routerDelegate as MyRouterDelegate;
|
||||
|
||||
bool get showDetailPage => _showDetailPage;
|
||||
bool _showDetailPage = false;
|
||||
set showDetailPage(bool value) {
|
||||
if (_showDetailPage == value) {
|
||||
return;
|
||||
}
|
||||
_showDetailPage = value;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<bool> _showConfirmDialog() async {
|
||||
return await showDialog<bool>(
|
||||
context: navigatorKey.currentContext!,
|
||||
barrierDismissible: false,
|
||||
builder: (BuildContext context) {
|
||||
return AlertDialog(
|
||||
title: const Text('Are you sure?'),
|
||||
actions: <Widget>[
|
||||
TextButton(
|
||||
child: const Text('Cancel'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(false);
|
||||
},
|
||||
),
|
||||
TextButton(
|
||||
child: const Text('Confirm'),
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop(true);
|
||||
},
|
||||
),
|
||||
],
|
||||
);
|
||||
},
|
||||
) ?? false;
|
||||
}
|
||||
|
||||
Future<void> _handlePopDetails(bool didPop, void result) async {
|
||||
if (didPop) {
|
||||
showDetailPage = false;
|
||||
return;
|
||||
}
|
||||
final bool confirmed = await _showConfirmDialog();
|
||||
if (confirmed) {
|
||||
showDetailPage = false;
|
||||
}
|
||||
}
|
||||
|
||||
List<Page<Object?>> _getPages() {
|
||||
return <Page<Object?>>[
|
||||
const MaterialPage<void>(key: ValueKey<String>('home'), child: _HomePage()),
|
||||
if (showDetailPage)
|
||||
MaterialPage<void>(
|
||||
key: const ValueKey<String>('details'),
|
||||
child: const _DetailsPage(),
|
||||
canPop: false,
|
||||
onPopInvoked: _handlePopDetails,
|
||||
),
|
||||
];
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Navigator(
|
||||
key: navigatorKey,
|
||||
pages: _getPages(),
|
||||
onDidRemovePage: (Page<Object?> page) {
|
||||
assert(page.key == const ValueKey<String>('details'));
|
||||
showDetailPage = false;
|
||||
},
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _HomePage extends StatefulWidget {
|
||||
const _HomePage();
|
||||
|
||||
@override
|
||||
State<_HomePage> createState() => _HomePageState();
|
||||
}
|
||||
|
||||
class _HomePageState extends State<_HomePage> {
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Home')),
|
||||
body: Center(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
MyRouterDelegate.of(context).showDetailPage = true;
|
||||
},
|
||||
child: const Text('Go to details'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _DetailsPage extends StatefulWidget {
|
||||
const _DetailsPage();
|
||||
|
||||
@override
|
||||
State<_DetailsPage> createState() => _DetailsPageState();
|
||||
}
|
||||
|
||||
class _DetailsPageState extends State<_DetailsPage> {
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Scaffold(
|
||||
appBar: AppBar(title: const Text('Details')),
|
||||
body: Center(
|
||||
child: TextButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).maybePop();
|
||||
},
|
||||
child: const Text('Go back'),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
54
examples/api/test/widgets/page/page_can_pop.0_test.dart
Normal file
54
examples/api/test/widgets/page/page_can_pop.0_test.dart
Normal file
|
@ -0,0 +1,54 @@
|
|||
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter_api_samples/widgets/page/page_can_pop.0.dart' as example;
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../navigator_utils.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Can choose to stay on page', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.PageApiExampleApp(),
|
||||
);
|
||||
|
||||
expect(find.text('Home'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Go to details'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Details'), findsOneWidget);
|
||||
|
||||
await simulateSystemBack();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Are you sure?'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Cancel'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Details'), findsOneWidget);
|
||||
});
|
||||
|
||||
testWidgets('Can choose to go back', (WidgetTester tester) async {
|
||||
await tester.pumpWidget(
|
||||
const example.PageApiExampleApp(),
|
||||
);
|
||||
|
||||
expect(find.text('Home'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Go to details'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Home'), findsNothing);
|
||||
expect(find.text('Details'), findsOneWidget);
|
||||
|
||||
await simulateSystemBack();
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Are you sure?'), findsOneWidget);
|
||||
|
||||
await tester.tap(find.text('Confirm'));
|
||||
await tester.pumpAndSettle();
|
||||
expect(find.text('Details'), findsNothing);
|
||||
expect(find.text('Home'), findsOneWidget);
|
||||
});
|
||||
}
|
|
@ -347,6 +347,8 @@ class CupertinoPage<T> extends Page<T> {
|
|||
this.title,
|
||||
this.fullscreenDialog = false,
|
||||
this.allowSnapshotting = true,
|
||||
super.canPop,
|
||||
super.onPopInvoked,
|
||||
super.key,
|
||||
super.name,
|
||||
super.arguments,
|
||||
|
|
|
@ -150,6 +150,8 @@ class MaterialPage<T> extends Page<T> {
|
|||
this.fullscreenDialog = false,
|
||||
this.allowSnapshotting = true,
|
||||
super.key,
|
||||
super.canPop,
|
||||
super.onPopInvoked,
|
||||
super.name,
|
||||
super.arguments,
|
||||
super.restorationId,
|
||||
|
|
|
@ -86,6 +86,14 @@ typedef WillPopCallback = Future<bool> Function();
|
|||
/// [Navigator.pages] list is next updated.)
|
||||
typedef PopPageCallback = bool Function(Route<dynamic> route, dynamic result);
|
||||
|
||||
/// Signature for the [Navigator.onDidRemovePage] callback.
|
||||
///
|
||||
/// This must properly update the pages list the next time it is passed into
|
||||
/// [Navigator.pages] so that it no longer includes the input `page`.
|
||||
/// (Otherwise, the page will be interpreted as a new page to show when the
|
||||
/// [Navigator.pages] list is next updated.)
|
||||
typedef DidRemovePageCallback = void Function(Page<Object?> page);
|
||||
|
||||
/// Indicates whether the current route should be popped.
|
||||
///
|
||||
/// Used as the return value for [Route.willPop].
|
||||
|
@ -173,6 +181,8 @@ abstract class Route<T> extends _RoutePlaceholder {
|
|||
RouteSettings get settings => _settings;
|
||||
RouteSettings _settings;
|
||||
|
||||
bool get _isPageBased => settings is Page<Object?>;
|
||||
|
||||
/// The restoration scope ID to be used for the [RestorationScope] surrounding
|
||||
/// this route.
|
||||
///
|
||||
|
@ -344,7 +354,14 @@ abstract class Route<T> extends _RoutePlaceholder {
|
|||
///
|
||||
/// * [Form], which provides a [Form.canPop] boolean that is similar.
|
||||
/// * [PopScope], a widget that provides a way to intercept the back button.
|
||||
/// * [Page.canPop], a way for [Page] to affect this property.
|
||||
RoutePopDisposition get popDisposition {
|
||||
if (_isPageBased) {
|
||||
final Page<Object?> page = settings as Page<Object?>;
|
||||
if (!page.canPop) {
|
||||
return RoutePopDisposition.doNotPop;
|
||||
}
|
||||
}
|
||||
return isFirst ? RoutePopDisposition.bubble : RoutePopDisposition.pop;
|
||||
}
|
||||
|
||||
|
@ -366,7 +383,13 @@ abstract class Route<T> extends _RoutePlaceholder {
|
|||
/// will still be called. The `didPop` parameter indicates whether or not the
|
||||
/// back navigation actually happened successfully.
|
||||
/// {@endtemplate}
|
||||
void onPopInvokedWithResult(bool didPop, T? result) => onPopInvoked(didPop);
|
||||
@mustCallSuper
|
||||
void onPopInvokedWithResult(bool didPop, T? result) {
|
||||
if (_isPageBased) {
|
||||
final Page<Object?> page = settings as Page<Object?>;
|
||||
page.onPopInvoked(didPop, result);
|
||||
}
|
||||
}
|
||||
|
||||
/// Whether calling [didPop] would return false.
|
||||
bool get willHandlePopInternally => false;
|
||||
|
@ -621,6 +644,15 @@ class RouteSettings {
|
|||
/// The type argument `T` is the corresponding [Route]'s return type, as
|
||||
/// used by [Route.currentResult], [Route.popped], and [Route.didPop].
|
||||
///
|
||||
/// The [canPop] and [onPopInvoked] are used for intercepting pops.
|
||||
///
|
||||
/// {@tool dartpad}
|
||||
/// This sample demonstrates how to use this [canPop] and [onPopInvoked] to
|
||||
/// intercept pops.
|
||||
///
|
||||
/// ** See code in examples/api/lib/widgets/page/page_can_pop.0.dart **
|
||||
/// {@end-tool}
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [Navigator.pages], which accepts a list of [Page]s and updates its routes
|
||||
|
@ -632,8 +664,12 @@ abstract class Page<T> extends RouteSettings {
|
|||
super.name,
|
||||
super.arguments,
|
||||
this.restorationId,
|
||||
this.canPop = true,
|
||||
this.onPopInvoked = _defaultPopInvokedHandler,
|
||||
});
|
||||
|
||||
static void _defaultPopInvokedHandler(bool didPop, Object? result) { }
|
||||
|
||||
/// The key associated with this page.
|
||||
///
|
||||
/// This key will be used for comparing pages in [canUpdate].
|
||||
|
@ -650,6 +686,28 @@ abstract class Page<T> extends RouteSettings {
|
|||
/// Flutter.
|
||||
final String? restorationId;
|
||||
|
||||
/// Called after a pop on the associated route was handled.
|
||||
///
|
||||
/// It's not possible to prevent the pop from happening at the time that this
|
||||
/// method is called; the pop has already happened. Use [canPop] to
|
||||
/// disable pops in advance.
|
||||
///
|
||||
/// This will still be called even when the pop is canceled. A pop is canceled
|
||||
/// when the associated [Route.popDisposition] returns false, or when
|
||||
/// [canPop] is set to false. The `didPop` parameter indicates whether or not
|
||||
/// the back navigation actually happened successfully.
|
||||
final PopInvokedWithResultCallback<T> onPopInvoked;
|
||||
|
||||
/// When false, blocks the associated route from being popped.
|
||||
///
|
||||
/// If this is set to false for first page in the Navigator. It prevents
|
||||
/// Flutter app from exiting.
|
||||
///
|
||||
/// If there are any [PopScope] widgets in a route's widget subtree,
|
||||
/// each of their `canPop` must be `true`, in addition to this canPop, in
|
||||
/// order for the route to be able to pop.
|
||||
final bool canPop;
|
||||
|
||||
/// Whether this page can be updated with the [other] page.
|
||||
///
|
||||
/// Two pages are consider updatable if they have same the [runtimeType] and
|
||||
|
@ -1465,6 +1523,10 @@ class Navigator extends StatefulWidget {
|
|||
const Navigator({
|
||||
super.key,
|
||||
this.pages = const <Page<dynamic>>[],
|
||||
@Deprecated(
|
||||
'Use onDidRemovePage instead. '
|
||||
'This feature was deprecated after v3.16.0-17.0.pre.',
|
||||
)
|
||||
this.onPopPage,
|
||||
this.initialRoute,
|
||||
this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
|
||||
|
@ -1477,6 +1539,7 @@ class Navigator extends StatefulWidget {
|
|||
this.requestFocus = true,
|
||||
this.restorationScopeId,
|
||||
this.routeTraversalEdgeBehavior = kDefaultRouteTraversalEdgeBehavior,
|
||||
this.onDidRemovePage,
|
||||
});
|
||||
|
||||
/// The list of pages with which to populate the history.
|
||||
|
@ -1509,6 +1572,8 @@ class Navigator extends StatefulWidget {
|
|||
/// corresponding to [pages] in the initial history.
|
||||
final List<Page<dynamic>> pages;
|
||||
|
||||
/// This is deprecated and replaced by [onDidRemovePage].
|
||||
///
|
||||
/// Called when [pop] is invoked but the current [Route] corresponds to a
|
||||
/// [Page] found in the [pages] list.
|
||||
///
|
||||
|
@ -1522,8 +1587,27 @@ class Navigator extends StatefulWidget {
|
|||
/// contain the [Page] for the given [Route]. The next time the [pages] list
|
||||
/// is updated, if the [Page] corresponding to this [Route] is still present,
|
||||
/// it will be interpreted as a new route to display.
|
||||
@Deprecated(
|
||||
'Use onDidRemovePage instead. '
|
||||
'This feature was deprecated after v3.16.0-17.0.pre.',
|
||||
)
|
||||
final PopPageCallback? onPopPage;
|
||||
|
||||
/// Called when the [Route] associated with the given [Page] has been removed
|
||||
/// from the Navigator.
|
||||
///
|
||||
/// This can happen when the route is removed or completed through
|
||||
/// [Navigator.pop], [Navigator.pushReplacement], or its friends.
|
||||
///
|
||||
/// This callback is responsible for removing the given page from the list of
|
||||
/// [pages].
|
||||
///
|
||||
/// The [Navigator] widget should be rebuilt with a [pages] list that does not
|
||||
/// contain the given page [Page]. The next time the [pages] list
|
||||
/// is updated, if the given [Page] is still present, it will be interpreted
|
||||
/// as a new page to display.
|
||||
final DidRemovePageCallback? onDidRemovePage;
|
||||
|
||||
/// The delegate used for deciding how routes transition in or off the screen
|
||||
/// during the [pages] updates.
|
||||
///
|
||||
|
@ -3084,6 +3168,11 @@ class _RouteEntry extends RouteTransitionRecord {
|
|||
currentState = _RouteLifecycle.idle;
|
||||
return false;
|
||||
}
|
||||
route.onPopInvokedWithResult(true, pendingResult);
|
||||
if (pageBased) {
|
||||
final Page<Object?> page = route.settings as Page<Object?>;
|
||||
navigator.widget.onDidRemovePage?.call(page);
|
||||
}
|
||||
pendingResult = null;
|
||||
return true;
|
||||
}
|
||||
|
@ -3120,7 +3209,6 @@ class _RouteEntry extends RouteTransitionRecord {
|
|||
assert(isPresent);
|
||||
pendingResult = result;
|
||||
currentState = _RouteLifecycle.pop;
|
||||
route.onPopInvokedWithResult(true, result);
|
||||
}
|
||||
|
||||
bool _reportRemovalToObserver = true;
|
||||
|
@ -3553,37 +3641,40 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||
}
|
||||
}
|
||||
|
||||
bool _debugCheckPageApiParameters() {
|
||||
if (!_usingPagesAPI) {
|
||||
return true;
|
||||
}
|
||||
if (widget.pages.isEmpty) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: FlutterError(
|
||||
'The Navigator.pages must not be empty to use the '
|
||||
'Navigator.pages API',
|
||||
),
|
||||
library: 'widget library',
|
||||
stack: StackTrace.current,
|
||||
),
|
||||
);
|
||||
} else if ((widget.onDidRemovePage == null) == (widget.onPopPage == null)) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: FlutterError(
|
||||
'Either onDidRemovePage or onPopPage must be provided to use the '
|
||||
'Navigator.pages API but not both.',
|
||||
),
|
||||
library: 'widget library',
|
||||
stack: StackTrace.current,
|
||||
),
|
||||
);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
assert(() {
|
||||
if (_usingPagesAPI) {
|
||||
if (widget.pages.isEmpty) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: FlutterError(
|
||||
'The Navigator.pages must not be empty to use the '
|
||||
'Navigator.pages API',
|
||||
),
|
||||
library: 'widget library',
|
||||
stack: StackTrace.current,
|
||||
),
|
||||
);
|
||||
} else if (widget.onPopPage == null) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: FlutterError(
|
||||
'The Navigator.onPopPage must be provided to use the '
|
||||
'Navigator.pages API',
|
||||
),
|
||||
library: 'widget library',
|
||||
stack: StackTrace.current,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
assert(_debugCheckPageApiParameters());
|
||||
for (final NavigatorObserver observer in widget.observers) {
|
||||
assert(observer.navigator == null);
|
||||
NavigatorObserver._navigators[observer] = this;
|
||||
|
@ -3790,35 +3881,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||
@override
|
||||
void didUpdateWidget(Navigator oldWidget) {
|
||||
super.didUpdateWidget(oldWidget);
|
||||
assert(() {
|
||||
if (_usingPagesAPI) {
|
||||
// This navigator uses page API.
|
||||
if (widget.pages.isEmpty) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: FlutterError(
|
||||
'The Navigator.pages must not be empty to use the '
|
||||
'Navigator.pages API',
|
||||
),
|
||||
library: 'widget library',
|
||||
stack: StackTrace.current,
|
||||
),
|
||||
);
|
||||
} else if (widget.onPopPage == null) {
|
||||
FlutterError.reportError(
|
||||
FlutterErrorDetails(
|
||||
exception: FlutterError(
|
||||
'The Navigator.onPopPage must be provided to use the '
|
||||
'Navigator.pages API',
|
||||
),
|
||||
library: 'widget library',
|
||||
stack: StackTrace.current,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}());
|
||||
assert(_debugCheckPageApiParameters());
|
||||
if (oldWidget.observers != widget.observers) {
|
||||
for (final NavigatorObserver observer in oldWidget.observers) {
|
||||
NavigatorObserver._navigators[observer] = null;
|
||||
|
@ -4321,6 +4384,9 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||
// We aren't allowed to remove this route yet.
|
||||
break;
|
||||
}
|
||||
if (entry.pageBased) {
|
||||
widget.onDidRemovePage?.call(entry.route.settings as Page<Object?>);
|
||||
}
|
||||
entry.currentState = _RouteLifecycle.dispose;
|
||||
continue;
|
||||
case _RouteLifecycle.dispose:
|
||||
|
@ -5229,14 +5295,14 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||
|
||||
// TODO(justinmc): When the deprecated willPop method is removed, delete
|
||||
// this code and use only popDisposition, below.
|
||||
final RoutePopDisposition willPopDisposition = await lastEntry.route.willPop();
|
||||
if (await lastEntry.route.willPop() == RoutePopDisposition.doNotPop) {
|
||||
return true;
|
||||
}
|
||||
if (!mounted) {
|
||||
// Forget about this pop, we were disposed in the meantime.
|
||||
return true;
|
||||
}
|
||||
if (willPopDisposition == RoutePopDisposition.doNotPop) {
|
||||
return true;
|
||||
}
|
||||
|
||||
final _RouteEntry? newLastEntry = _lastRouteEntryWhereOrNull(_RouteEntry.isPresentPredicate);
|
||||
if (lastEntry != newLastEntry) {
|
||||
// Forget about this pop, something happened to our history in the meantime.
|
||||
|
@ -5287,7 +5353,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
|
|||
return true;
|
||||
}());
|
||||
final _RouteEntry entry = _history.lastWhere(_RouteEntry.isPresentPredicate);
|
||||
if (entry.pageBased) {
|
||||
if (entry.pageBased && widget.onPopPage != null) {
|
||||
if (widget.onPopPage!(entry.route, result) && entry.currentState == _RouteLifecycle.idle) {
|
||||
// The entry may have been disposed if the pop finishes synchronously.
|
||||
assert(entry.route._popCompleter.isCompleted);
|
||||
|
|
|
@ -1740,6 +1740,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||
for (final PopEntry<Object?> popEntry in _popEntries) {
|
||||
popEntry.onPopInvokedWithResult(didPop, result);
|
||||
}
|
||||
super.onPopInvokedWithResult(didPop, result);
|
||||
}
|
||||
|
||||
/// Enables this route to veto attempts by the user to dismiss it.
|
||||
|
@ -1797,8 +1798,8 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||
/// * [unregisterPopEntry], which performs the opposite operation.
|
||||
void registerPopEntry(PopEntry<Object?> popEntry) {
|
||||
_popEntries.add(popEntry);
|
||||
popEntry.canPopNotifier.addListener(_handlePopEntryChange);
|
||||
_handlePopEntryChange();
|
||||
popEntry.canPopNotifier.addListener(_maybeDispatchNavigationNotification);
|
||||
_maybeDispatchNavigationNotification();
|
||||
}
|
||||
|
||||
/// Unregisters a [PopEntry] in the route's widget subtree.
|
||||
|
@ -1808,11 +1809,11 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||
/// * [registerPopEntry], which performs the opposite operation.
|
||||
void unregisterPopEntry(PopEntry<Object?> popEntry) {
|
||||
_popEntries.remove(popEntry);
|
||||
popEntry.canPopNotifier.removeListener(_handlePopEntryChange);
|
||||
_handlePopEntryChange();
|
||||
popEntry.canPopNotifier.removeListener(_maybeDispatchNavigationNotification);
|
||||
_maybeDispatchNavigationNotification();
|
||||
}
|
||||
|
||||
void _handlePopEntryChange() {
|
||||
void _maybeDispatchNavigationNotification() {
|
||||
if (!isCurrent) {
|
||||
return;
|
||||
}
|
||||
|
@ -1881,6 +1882,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
|
|||
void didPopNext(Route<dynamic> nextRoute) {
|
||||
super.didPopNext(nextRoute);
|
||||
changedInternalState();
|
||||
_maybeDispatchNavigationNotification();
|
||||
}
|
||||
|
||||
@override
|
||||
|
|
|
@ -2894,8 +2894,8 @@ void main() {
|
|||
error.toStringDeep(),
|
||||
equalsIgnoringHashCodes(
|
||||
'FlutterError\n'
|
||||
' The Navigator.onPopPage must be provided to use the\n'
|
||||
' Navigator.pages API\n',
|
||||
' Either onDidRemovePage or onPopPage must be provided to use the\n'
|
||||
' Navigator.pages API but not both.\n',
|
||||
),
|
||||
);
|
||||
});
|
||||
|
|
Loading…
Reference in a new issue