diff --git a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart index a063c7cfe4..a68c2a194c 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart @@ -48,7 +48,7 @@ class CupertinoNavigationDemo extends StatelessWidget { @override Widget build(BuildContext context) { - return PopScope( + return PopScope( // Prevent swipe popping of this page. Use explicit exit buttons only. canPop: false, child: DefaultTextStyle( diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart index 140211387c..81ee4dacd8 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart @@ -110,7 +110,7 @@ class FullScreenDialogDemoState extends State { bool _hasName = false; late String _eventName; - Future _handlePopInvoked(bool didPop, Object? result) async { + Future _handlePopInvoked(bool didPop) async { if (didPop) { return; } @@ -175,7 +175,7 @@ class FullScreenDialogDemoState extends State { ), body: Form( canPop: !_saveNeeded && !_hasLocation && !_hasName, - onPopInvokedWithResult: _handlePopInvoked, + onPopInvoked: _handlePopInvoked, child: Scrollbar( child: ListView( primary: true, diff --git a/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart b/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart index 47811d81c5..c6f644ee74 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart @@ -143,7 +143,7 @@ class TextFormFieldDemoState extends State { return null; } - Future _handlePopInvoked(bool didPop, Object? result) async { + Future _handlePopInvoked(bool didPop) async { if (didPop) { return; } @@ -192,7 +192,7 @@ class TextFormFieldDemoState extends State { key: _formKey, autovalidateMode: _autovalidateMode, canPop: _formKey.currentState == null || !_formWasEdited || _formKey.currentState!.validate(), - onPopInvokedWithResult: _handlePopInvoked, + onPopInvoked: _handlePopInvoked, child: Scrollbar( child: SingleChildScrollView( primary: true, diff --git a/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart b/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart index 0dfbcbe975..5e7a951668 100644 --- a/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart +++ b/dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart @@ -355,7 +355,7 @@ class ExpandingBottomSheetState extends State with TickerP // Closes the cart if the cart is open, otherwise exits the app (this should // only be relevant for Android). - void _handlePopInvoked(bool didPop, Object? result) { + void _handlePopInvoked(bool didPop) { if (didPop) { return; } @@ -370,9 +370,9 @@ class ExpandingBottomSheetState extends State with TickerP duration: const Duration(milliseconds: 225), curve: Curves.easeInOut, alignment: FractionalOffset.topLeft, - child: PopScope( + child: PopScope( canPop: !_isOpen, - onPopInvokedWithResult: _handlePopInvoked, + onPopInvoked: _handlePopInvoked, child: AnimatedBuilder( animation: widget.hideController, builder: _buildSlideAnimation, diff --git a/dev/integration_tests/flutter_gallery/lib/gallery/home.dart b/dev/integration_tests/flutter_gallery/lib/gallery/home.dart index fc7c4d8005..a6ceab8850 100644 --- a/dev/integration_tests/flutter_gallery/lib/gallery/home.dart +++ b/dev/integration_tests/flutter_gallery/lib/gallery/home.dart @@ -326,9 +326,9 @@ class _GalleryHomeState extends State with SingleTickerProviderStat backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor, body: SafeArea( bottom: false, - child: PopScope( + child: PopScope( canPop: _category == null, - onPopInvokedWithResult: (bool didPop, Object? result) { + onPopInvoked: (bool didPop) { if (didPop) { return; } diff --git a/examples/api/lib/widgets/form/form.1.dart b/examples/api/lib/widgets/form/form.1.dart index a7685152ab..e008f5aaa4 100644 --- a/examples/api/lib/widgets/form/form.1.dart +++ b/examples/api/lib/widgets/form/form.1.dart @@ -111,7 +111,7 @@ class _SaveableFormState extends State<_SaveableForm> { const SizedBox(height: 20.0), Form( canPop: !_isDirty, - onPopInvokedWithResult: (bool didPop, Object? result) async { + onPopInvoked: (bool didPop) async { if (didPop) { return; } diff --git a/examples/api/lib/widgets/pop_scope/pop_scope.0.dart b/examples/api/lib/widgets/pop_scope/pop_scope.0.dart index e2ed446259..2400b0905e 100644 --- a/examples/api/lib/widgets/pop_scope/pop_scope.0.dart +++ b/examples/api/lib/widgets/pop_scope/pop_scope.0.dart @@ -109,9 +109,9 @@ class _PageTwoState extends State<_PageTwo> { mainAxisAlignment: MainAxisAlignment.center, children: [ const Text('Page Two'), - PopScope( + PopScope( canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) async { + onPopInvoked: (bool didPop) async { if (didPop) { return; } diff --git a/examples/api/lib/widgets/pop_scope/pop_scope.1.dart b/examples/api/lib/widgets/pop_scope/pop_scope.1.dart deleted file mode 100644 index 7a058837a5..0000000000 --- a/examples/api/lib/widgets/pop_scope/pop_scope.1.dart +++ /dev/null @@ -1,233 +0,0 @@ -// 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 how to use a PopScope to wrap a widget that -// may pop the page with a result. - -import 'package:flutter/material.dart'; - -void main() => runApp(const NavigatorPopHandlerApp()); - -class NavigatorPopHandlerApp extends StatelessWidget { - const NavigatorPopHandlerApp({super.key}); - - @override - Widget build(BuildContext context) { - return MaterialApp( - initialRoute: '/home', - onGenerateRoute: (RouteSettings settings) { - return switch (settings.name) { - '/two' => MaterialPageRoute( - builder: (BuildContext context) => const _PageTwo(), - ), - _ => MaterialPageRoute( - builder: (BuildContext context) => const _HomePage(), - ), - }; - }, - ); - } -} - -class _HomePage extends StatefulWidget { - const _HomePage(); - - @override - State<_HomePage> createState() => _HomePageState(); -} - -class _HomePageState extends State<_HomePage> { - FormData? _formData; - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Page One'), - if (_formData != null) - Text('Hello ${_formData!.name}, whose favorite food is ${_formData!.favoriteFood}.'), - TextButton( - onPressed: () async { - final FormData formData = - await Navigator.of(context).pushNamed('/two') - ?? const FormData(); - if (formData != _formData) { - setState(() { - _formData = formData; - }); - } - }, - child: const Text('Next page'), - ), - ], - ), - ), - ); - } -} - -class _PopScopeWrapper extends StatelessWidget { - const _PopScopeWrapper({required this.child}); - - final Widget child; - - Future _showBackDialog(BuildContext context) { - return showDialog( - context: context, - builder: (BuildContext context) { - return AlertDialog( - title: const Text('Are you sure?'), - content: const Text( - 'Are you sure you want to leave this page?', - ), - actions: [ - TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), - child: const Text('Never mind'), - onPressed: () { - Navigator.pop(context, false); - }, - ), - TextButton( - style: TextButton.styleFrom( - textStyle: Theme.of(context).textTheme.labelLarge, - ), - child: const Text('Leave'), - onPressed: () { - Navigator.pop(context, true); - }, - ), - ], - ); - }, - ); - } - - @override - Widget build(BuildContext context) { - return PopScope( - canPop: false, - // The result argument contains the pop result that is defined in `_PageTwo`. - onPopInvokedWithResult: (bool didPop, FormData? result) async { - if (didPop) { - return; - } - final bool shouldPop = await _showBackDialog(context) ?? false; - if (context.mounted && shouldPop) { - Navigator.pop(context, result); - } - }, - child: child, - ); - } -} - -// This is a PopScope wrapper over _PageTwoBody -class _PageTwo extends StatelessWidget { - const _PageTwo(); - - @override - Widget build(BuildContext context) { - return const _PopScopeWrapper( - child: _PageTwoBody(), - ); - } - -} - -class _PageTwoBody extends StatefulWidget { - const _PageTwoBody(); - - @override - State<_PageTwoBody> createState() => _PageTwoBodyState(); -} - -class _PageTwoBodyState extends State<_PageTwoBody> { - FormData _formData = const FormData(); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - const Text('Page Two'), - Form( - child: Column( - children: [ - TextFormField( - decoration: const InputDecoration( - hintText: 'Enter your name.', - ), - onChanged: (String value) { - _formData = _formData.copyWith( - name: value, - ); - }, - ), - TextFormField( - decoration: const InputDecoration( - hintText: 'Enter your favorite food.', - ), - onChanged: (String value) { - _formData = _formData.copyWith( - favoriteFood: value, - ); - }, - ), - ], - ), - ), - TextButton( - onPressed: () async { - Navigator.maybePop(context, _formData); - }, - child: const Text('Go back'), - ), - ], - ), - ), - ); - } -} - -@immutable -class FormData { - const FormData({ - this.name = '', - this.favoriteFood = '', - }); - - final String name; - final String favoriteFood; - - FormData copyWith({String? name, String? favoriteFood}) { - return FormData( - name: name ?? this.name, - favoriteFood: favoriteFood ?? this.favoriteFood, - ); - } - - @override - bool operator ==(Object other) { - if (identical(this, other)) { - return true; - } - if (other.runtimeType != runtimeType) { - return false; - } - return other is FormData - && other.name == name - && other.favoriteFood == favoriteFood; - } - - @override - int get hashCode => Object.hash(name, favoriteFood); -} diff --git a/examples/api/test/widgets/pop_scope/pop_scope.1_test.dart b/examples/api/test/widgets/pop_scope/pop_scope.1_test.dart deleted file mode 100644 index 14266af521..0000000000 --- a/examples/api/test/widgets/pop_scope/pop_scope.1_test.dart +++ /dev/null @@ -1,67 +0,0 @@ -// 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/material.dart'; -import 'package:flutter_api_samples/widgets/pop_scope/pop_scope.1.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.NavigatorPopHandlerApp(), - ); - - expect(find.text('Page One'), findsOneWidget); - - await tester.tap(find.text('Next page')); - await tester.pumpAndSettle(); - expect(find.text('Page One'), findsNothing); - expect(find.text('Page Two'), findsOneWidget); - - await simulateSystemBack(); - await tester.pumpAndSettle(); - expect(find.text('Page One'), findsNothing); - expect(find.text('Page Two'), findsOneWidget); - expect(find.text('Are you sure?'), findsOneWidget); - - await tester.tap(find.text('Never mind')); - await tester.pumpAndSettle(); - expect(find.text('Page One'), findsNothing); - expect(find.text('Page Two'), findsOneWidget); - }); - - testWidgets('Can choose to go back with pop result', (WidgetTester tester) async { - await tester.pumpWidget( - const example.NavigatorPopHandlerApp(), - ); - - expect(find.text('Page One'), findsOneWidget); - expect(find.text('Page Two'), findsNothing); - - await tester.tap(find.text('Next page')); - await tester.pumpAndSettle(); - expect(find.text('Page One'), findsNothing); - expect(find.text('Page Two'), findsOneWidget); - - await tester.enterText(find.byType(TextFormField).first, 'John'); - await tester.pumpAndSettle(); - await tester.enterText(find.byType(TextFormField).last, 'Apple'); - await tester.pumpAndSettle(); - - await tester.tap(find.text('Go back')); - await tester.pumpAndSettle(); - expect(find.text('Page One'), findsNothing); - expect(find.text('Page Two'), findsOneWidget); - expect(find.text('Are you sure?'), findsOneWidget); - - await tester.tap(find.text('Leave')); - await tester.pumpAndSettle(); - expect(find.text('Page One'), findsOneWidget); - expect(find.text('Page Two'), findsNothing); - expect(find.text('Are you sure?'), findsNothing); - expect(find.text('Hello John, whose favorite food is Apple.'), findsOneWidget); - }); -} diff --git a/packages/flutter/lib/src/material/about.dart b/packages/flutter/lib/src/material/about.dart index 67c1a1ae13..a61f3a0d79 100644 --- a/packages/flutter/lib/src/material/about.dart +++ b/packages/flutter/lib/src/material/about.dart @@ -1230,9 +1230,9 @@ class _MasterDetailFlowState extends State<_MasterDetailFlow> implements _PageOp } MaterialPageRoute _detailPageRoute(Object? arguments) { - return MaterialPageRoute(builder: (BuildContext context) { - return PopScope( - onPopInvokedWithResult: (bool didPop, void result) { + return MaterialPageRoute(builder: (BuildContext context) { + return PopScope( + onPopInvoked: (bool didPop) { // No need for setState() as rebuild happens on navigation pop. focus = _Focus.master; }, diff --git a/packages/flutter/lib/src/widgets/form.dart b/packages/flutter/lib/src/widgets/form.dart index f17529b8b3..5277e8a603 100644 --- a/packages/flutter/lib/src/widgets/form.dart +++ b/packages/flutter/lib/src/widgets/form.dart @@ -55,22 +55,16 @@ class Form extends StatefulWidget { super.key, required this.child, this.canPop, - @Deprecated( - 'Use onPopInvokedWithResult instead. ' - 'This feature was deprecated after v3.22.0-12.0.pre.', - ) this.onPopInvoked, - this.onPopInvokedWithResult, @Deprecated( - 'Use canPop and/or onPopInvokedWithResult instead. ' + 'Use canPop and/or onPopInvoked instead. ' 'This feature was deprecated after v3.12.0-1.0.pre.', ) this.onWillPop, this.onChanged, AutovalidateMode? autovalidateMode, }) : autovalidateMode = autovalidateMode ?? AutovalidateMode.disabled, - assert(onPopInvokedWithResult == null || onPopInvoked == null, 'onPopInvoked is deprecated; use onPopInvokedWithResult'), - assert(((onPopInvokedWithResult ?? onPopInvoked ?? canPop) == null) || onWillPop == null, 'onWillPop is deprecated; use canPop and/or onPopInvokedWithResult.'); + assert((onPopInvoked == null && canPop == null) || onWillPop == null, 'onWillPop is deprecated; use canPop and/or onPopInvoked.'); /// Returns the [FormState] of the closest [Form] widget which encloses the /// given context, or null if none is found. @@ -150,7 +144,7 @@ class Form extends StatefulWidget { /// * [WillPopScope], another widget that provides a way to intercept the /// back button. @Deprecated( - 'Use canPop and/or onPopInvokedWithResult instead. ' + 'Use canPop and/or onPopInvoked instead. ' 'This feature was deprecated after v3.12.0-1.0.pre.', ) final WillPopCallback? onWillPop; @@ -166,18 +160,11 @@ class Form extends StatefulWidget { /// /// See also: /// - /// * [onPopInvokedWithResult], which also comes from [PopScope] and is often used in + /// * [onPopInvoked], which also comes from [PopScope] and is often used in /// conjunction with this parameter. /// * [PopScope.canPop], which is what [Form] delegates to internally. final bool? canPop; - /// {@macro flutter.widgets.navigator.onPopInvoked} - @Deprecated( - 'Use onPopInvokedWithResult instead. ' - 'This feature was deprecated after v3.22.0-12.0.pre.', - ) - final PopInvokedCallback? onPopInvoked; - /// {@macro flutter.widgets.navigator.onPopInvoked} /// /// {@tool dartpad} @@ -191,8 +178,8 @@ class Form extends StatefulWidget { /// /// * [canPop], which also comes from [PopScope] and is often used in /// conjunction with this parameter. - /// * [PopScope.onPopInvokedWithResult], which is what [Form] delegates to internally. - final PopInvokedWithResultCallback? onPopInvokedWithResult; + /// * [PopScope.onPopInvoked], which is what [Form] delegates to internally. + final PopInvokedCallback? onPopInvoked; /// Called when one of the form fields changes. /// @@ -206,14 +193,6 @@ class Form extends StatefulWidget { /// {@macro flutter.widgets.FormField.autovalidateMode} final AutovalidateMode autovalidateMode; - void _callPopInvoked(bool didPop, Object? result) { - if (onPopInvokedWithResult != null) { - onPopInvokedWithResult!(didPop, result); - return; - } - onPopInvoked?.call(didPop); - } - @override FormState createState() => FormState(); } @@ -279,10 +258,10 @@ class FormState extends State
{ break; } - if (widget.canPop != null || (widget.onPopInvokedWithResult ?? widget.onPopInvoked) != null) { - return PopScope( + if (widget.canPop != null || widget.onPopInvoked != null) { + return PopScope( canPop: widget.canPop ?? true, - onPopInvokedWithResult: widget._callPopInvoked, + onPopInvoked: widget.onPopInvoked, child: _FormScope( formState: this, generation: _generation, diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index bed9a85ac1..662f12d335 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -355,7 +355,7 @@ abstract class Route extends _RoutePlaceholder { /// will still be called. The `didPop` parameter indicates whether or not the /// back navigation actually happened successfully. /// {@endtemplate} - void onPopInvoked(bool didPop, T? result) {} + void onPopInvoked(bool didPop) {} /// Whether calling [didPop] would return false. bool get willHandlePopInternally => false; @@ -3109,7 +3109,7 @@ class _RouteEntry extends RouteTransitionRecord { assert(isPresent); pendingResult = result; currentState = _RouteLifecycle.pop; - route.onPopInvoked(true, result); + route.onPopInvoked(true); } bool _reportRemovalToObserver = true; @@ -5239,7 +5239,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res pop(result); return true; case RoutePopDisposition.doNotPop: - lastEntry.route.onPopInvoked(false, result); + lastEntry.route.onPopInvoked(false); return true; } } @@ -5282,7 +5282,7 @@ class NavigatorState extends State with TickerProviderStateMixin, Res assert(entry.route._popCompleter.isCompleted); entry.currentState = _RouteLifecycle.pop; } - entry.route.onPopInvoked(true, result); + entry.route.onPopInvoked(true); } else { entry.pop(result); assert (entry.currentState == _RouteLifecycle.pop); diff --git a/packages/flutter/lib/src/widgets/navigator_pop_handler.dart b/packages/flutter/lib/src/widgets/navigator_pop_handler.dart index ea2be66955..203a85bede 100644 --- a/packages/flutter/lib/src/widgets/navigator_pop_handler.dart +++ b/packages/flutter/lib/src/widgets/navigator_pop_handler.dart @@ -81,9 +81,9 @@ class _NavigatorPopHandlerState extends State { Widget build(BuildContext context) { // When the widget subtree indicates it can handle a pop, disable popping // here, so that it can be manually handled in canPop. - return PopScope( + return PopScope( canPop: !widget.enabled || _canPop, - onPopInvokedWithResult: (bool didPop, Object? result) { + onPopInvoked: (bool didPop) { if (didPop) { return; } diff --git a/packages/flutter/lib/src/widgets/pop_scope.dart b/packages/flutter/lib/src/widgets/pop_scope.dart index 8bbac4ce39..c8b31f60c8 100644 --- a/packages/flutter/lib/src/widgets/pop_scope.dart +++ b/packages/flutter/lib/src/widgets/pop_scope.dart @@ -8,29 +8,12 @@ import 'framework.dart'; import 'navigator.dart'; import 'routes.dart'; -/// A callback type for informing that a navigation pop has been invoked, -/// whether or not it was handled successfully. -/// -/// Accepts a didPop boolean indicating whether or not back navigation -/// succeeded. -@Deprecated( - 'Use PopWithResultInvokedCallback instead. ' - 'This feature was deprecated after v3.22.0-12.0.pre.', -) -typedef PopInvokedCallback = void Function(bool didPop); - /// Manages back navigation gestures. /// -/// The generic type should match or be a supertype of the generic type of the -/// enclosing [Route]. For example, if the enclosing Route is a -/// `MaterialPageRoute`, you can define [PopScope] with `int` or any -/// supertype of `int`. -/// /// The [canPop] parameter disables back gestures when set to `false`. /// -/// The [onPopInvokedWithResult] parameter reports when pop navigation was attempted, and -/// `didPop` indicates whether or not the navigation was successful. The -/// `result` contains the pop result. +/// The [onPopInvoked] parameter reports when pop navigation was attempted, and +/// `didPop` indicates whether or not the navigation was successful. /// /// Android has a system back gesture that is a swipe inward from near the edge /// of the screen. It is recognized by Android before being passed to Flutter. @@ -39,15 +22,15 @@ typedef PopInvokedCallback = void Function(bool didPop); /// back gesture. /// /// If [canPop] is false, then a system back gesture will not pop the route off -/// of the enclosing [Navigator]. [onPopInvokedWithResult] will still be called, and +/// of the enclosing [Navigator]. [onPopInvoked] will still be called, and /// `didPop` will be `false`. On iOS when using [CupertinoRouteTransitionMixin] /// with [canPop] set to false, no gesture will be detected at all, so -/// [onPopInvokedWithResult] will not be called. Programmatically attempting pop -/// navigation will also result in a call to [onPopInvokedWithResult], with `didPop` +/// [onPopInvoked] will not be called. Programmatically attempting pop +/// navigation will also result in a call to [onPopInvoked], with `didPop` /// indicating success or failure. /// /// If [canPop] is true, then a system back gesture will cause the enclosing -/// [Navigator] to receive a pop as usual. [onPopInvokedWithResult] will be called with +/// [Navigator] to receive a pop as usual. [onPopInvoked] will be called with /// `didPop` as true, unless the pop failed for reasons unrelated to /// [PopScope], in which case it will be false. /// @@ -58,42 +41,30 @@ typedef PopInvokedCallback = void Function(bool didPop); /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.0.dart ** /// {@end-tool} /// -/// {@tool dartpad} -/// This sample demonstrates showing how to use PopScope to wrap widget that -/// may pop the page with a result. -/// -/// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.1.dart ** -/// {@end-tool} -/// /// See also: /// /// * [NavigatorPopHandler], which is a less verbose way to handle system back /// gestures in simple cases of nested [Navigator]s. -/// * [Form.canPop] and [Form.onPopInvokedWithResult], which can be used to handle system +/// * [Form.canPop] and [Form.onPopInvoked], which can be used to handle system /// back gestures in the case of a form with unsaved data. /// * [ModalRoute.registerPopEntry] and [ModalRoute.unregisterPopEntry], /// which this widget uses to integrate with Flutter's navigation system. -class PopScope extends StatefulWidget { +class PopScope extends StatefulWidget { /// Creates a widget that registers a callback to veto attempts by the user to /// dismiss the enclosing [ModalRoute]. const PopScope({ super.key, required this.child, this.canPop = true, - this.onPopInvokedWithResult, - @Deprecated( - 'Use onPopInvokedWithResult instead. ' - 'This feature was deprecated after v3.22.0-12.0.pre.', - ) this.onPopInvoked, - }) : assert(onPopInvokedWithResult == null || onPopInvoked == null, 'onPopInvoked is deprecated, use onPopInvokedWithResult'); + }); /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; - /// {@template flutter.widgets.PopScope.onPopInvokedWithResult} + /// {@template flutter.widgets.PopScope.onPopInvoked} /// Called after a route pop was handled. /// {@endtemplate} /// @@ -107,38 +78,11 @@ class PopScope extends StatefulWidget { /// indicates whether or not the back navigation actually happened /// successfully. /// - /// The `result` contains the pop result. - /// /// See also: /// /// * [Route.onPopInvoked], which is similar. - final PopInvokedWithResultCallback? onPopInvokedWithResult; - - /// Called after a route pop 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 relevant [Route.popDisposition] returns false, such as when - /// [canPop] is set to false on a [PopScope]. The `didPop` parameter - /// indicates whether or not the back navigation actually happened - /// successfully. - @Deprecated( - 'Use onPopInvokedWithResult instead. ' - 'This feature was deprecated after v3.22.0-12.0.pre.', - ) final PopInvokedCallback? onPopInvoked; - void _callPopInvoked(bool didPop, T? result) { - if (onPopInvokedWithResult != null) { - onPopInvokedWithResult!(didPop, result); - return; - } - onPopInvoked?.call(didPop); - } - /// {@template flutter.widgets.PopScope.canPop} /// When false, blocks the current route from being popped. /// @@ -155,16 +99,14 @@ class PopScope extends StatefulWidget { final bool canPop; @override - State> createState() => _PopScopeState(); + State createState() => _PopScopeState(); } -class _PopScopeState extends State> implements PopEntry { +class _PopScopeState extends State implements PopEntry { ModalRoute? _route; @override - void onPopInvoked(bool didPop, T? result) { - widget._callPopInvoked(didPop, result); - } + PopInvokedCallback? get onPopInvoked => widget.onPopInvoked; @override late final ValueNotifier canPopNotifier; @@ -187,7 +129,7 @@ class _PopScopeState extends State> implements PopEntry { } @override - void didUpdateWidget(PopScope oldWidget) { + void didUpdateWidget(PopScope oldWidget) { super.didUpdateWidget(oldWidget); canPopNotifier.value = widget.canPop; } diff --git a/packages/flutter/lib/src/widgets/routes.dart b/packages/flutter/lib/src/widgets/routes.dart index 89e3af2d8e..6e16448d6e 100644 --- a/packages/flutter/lib/src/widgets/routes.dart +++ b/packages/flutter/lib/src/widgets/routes.dart @@ -1669,9 +1669,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute _willPopCallbacks = []; - // Holding as Object? instead of T so that PopScope in this route can be - // declared with any supertype of T. - final Set> _popEntries = >{}; + final Set _popEntries = {}; /// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with /// [addScopedWillPopCallback] returns either false or null. If they all @@ -1719,14 +1717,14 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry in _popEntries) { + for (final PopEntry popEntry in _popEntries) { if (!popEntry.canPopNotifier.value) { return RoutePopDisposition.doNotPop; } @@ -1736,9 +1734,9 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry in _popEntries) { - popEntry.onPopInvoked(didPop, result); + void onPopInvoked(bool didPop) { + for (final PopEntry popEntry in _popEntries) { + popEntry.onPopInvoked?.call(didPop); } } @@ -1795,7 +1793,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry) { + void registerPopEntry(PopEntry popEntry) { _popEntries.add(popEntry); popEntry.canPopNotifier.addListener(_handlePopEntryChange); _handlePopEntryChange(); @@ -1806,7 +1804,7 @@ abstract class ModalRoute extends TransitionRoute with LocalHistoryRoute popEntry) { + void unregisterPopEntry(PopEntry popEntry) { _popEntries.remove(popEntry); popEntry.canPopNotifier.removeListener(_handlePopEntryChange); _handlePopEntryChange(); @@ -2415,9 +2413,7 @@ typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animatio /// /// Accepts a didPop boolean indicating whether or not back navigation /// succeeded. -/// -/// The `result` contains the pop result. -typedef PopInvokedWithResultCallback = void Function(bool didPop, T? result); +typedef PopInvokedCallback = void Function(bool didPop); /// Allows listening to and preventing pops. /// @@ -2429,9 +2425,9 @@ typedef PopInvokedWithResultCallback = void Function(bool didPop, T? result); /// * [PopScope], which provides similar functionality in a widget. /// * [ModalRoute.registerPopEntry], which unregisters instances of this. /// * [ModalRoute.unregisterPopEntry], which unregisters instances of this. -abstract class PopEntry { - /// {@macro flutter.widgets.PopScope.onPopInvokedWithResult} - void onPopInvoked(bool didPop, T? result); +abstract class PopEntry { + /// {@macro flutter.widgets.PopScope.onPopInvoked} + PopInvokedCallback? get onPopInvoked; /// {@macro flutter.widgets.PopScope.canPop} ValueListenable get canPopNotifier; diff --git a/packages/flutter/test/cupertino/tab_test.dart b/packages/flutter/test/cupertino/tab_test.dart index 7188777509..5acf173cd6 100644 --- a/packages/flutter/test/cupertino/tab_test.dart +++ b/packages/flutter/test/cupertino/tab_test.dart @@ -305,7 +305,7 @@ void main() { BottomNavigationBarItem(label: '', icon: Text('2')) ], ), - tabBuilder: (_, int i) => PopScope( + tabBuilder: (_, int i) => PopScope( canPop: false, child: CupertinoTabView( navigatorKey: key, diff --git a/packages/flutter/test/widgets/navigator_test.dart b/packages/flutter/test/widgets/navigator_test.dart index 3407f61dcf..0bb9ba9d5d 100644 --- a/packages/flutter/test/widgets/navigator_test.dart +++ b/packages/flutter/test/widgets/navigator_test.dart @@ -2989,7 +2989,7 @@ void main() { const List> myPages = >[ MaterialPage(child: Text('page1')), MaterialPage( - child: PopScope( + child: PopScope( canPop: false, child: Text('page2'), ), @@ -4908,9 +4908,9 @@ void main() { home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { builderSetState = setState; - return PopScope( + return PopScope( canPop: canPop(), - onPopInvokedWithResult: (bool success, Object? result) { + onPopInvoked: (bool success) { if (success || pages.last == _Page.noPop) { return; } @@ -5024,9 +5024,9 @@ void main() { MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return PopScope( + return PopScope( canPop: canPop(), - onPopInvokedWithResult: (bool success, Object? result) { + onPopInvoked: (bool success) { if (success || pages.last == _Page.noPop) { return; } @@ -5117,9 +5117,9 @@ void main() { MaterialApp( home: StatefulBuilder( builder: (BuildContext context, StateSetter setState) { - return PopScope( + return PopScope( canPop: canPop(), - onPopInvokedWithResult: (bool success, Object? result) { + onPopInvoked: (bool success) { if (success || pages.last == _PageWithYesPop.noPop) { return; } @@ -5189,7 +5189,7 @@ void main() { child: _LinksPage( title: 'Can pop page', canPop: true, - onPopInvoked: (bool didPop, void result) { + onPopInvoked: (bool didPop) { onPopInvokedCallCount += 1; }, ), @@ -5556,7 +5556,7 @@ class _LinksPage extends StatelessWidget { final bool? canPop; final VoidCallback? onBack; final String title; - final PopInvokedWithResultCallback? onPopInvoked; + final PopInvokedCallback? onPopInvoked; @override Widget build(BuildContext context) { @@ -5575,9 +5575,9 @@ class _LinksPage extends StatelessWidget { child: const Text('Go back'), ), if (canPop != null) - PopScope( + PopScope( canPop: canPop!, - onPopInvokedWithResult: onPopInvoked, + onPopInvoked: onPopInvoked, child: const SizedBox.shrink(), ), ], diff --git a/packages/flutter/test/widgets/pop_scope_test.dart b/packages/flutter/test/widgets/pop_scope_test.dart index 8e6d104053..116951ce78 100644 --- a/packages/flutter/test/widgets/pop_scope_test.dart +++ b/packages/flutter/test/widgets/pop_scope_test.dart @@ -47,7 +47,7 @@ void main() { builder: (BuildContext buildContext, StateSetter stateSetter) { context = buildContext; setState = stateSetter; - return PopScope( + return PopScope( canPop: canPop, child: const Center( child: Column( @@ -79,94 +79,6 @@ void main() { variant: TargetPlatformVariant.all(), ); - testWidgets('pop scope can receive result', (WidgetTester tester) async { - Object? receivedResult; - final Object poppedResult = Object(); - final GlobalKey nav = GlobalKey(); - await tester.pumpWidget( - MaterialApp( - initialRoute: '/', - navigatorKey: nav, - home: Scaffold( - body: PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) { - receivedResult = result; - }, - child: const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Home/PopScope Page'), - ], - ), - ), - ), - ), - ), - ); - - nav.currentState!.maybePop(poppedResult); - await tester.pumpAndSettle(); - expect(receivedResult, poppedResult); - }, - variant: TargetPlatformVariant.all(), - ); - - testWidgets('pop scope can have Object? generic type while route has stricter generic type', (WidgetTester tester) async { - Object? receivedResult; - const int poppedResult = 13; - final GlobalKey nav = GlobalKey(); - await tester.pumpWidget( - MaterialApp( - initialRoute: '/', - navigatorKey: nav, - home: Scaffold( - body: PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) { - receivedResult = result; - }, - child: const Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: [ - Text('Home/PopScope Page'), - ], - ), - ), - ), - ), - ), - ); - - nav.currentState!.push( - MaterialPageRoute( - builder: (BuildContext context) { - return Scaffold( - body: PopScope( - canPop: false, - onPopInvokedWithResult: (bool didPop, Object? result) { - receivedResult = result; - }, - child: const Center( - child: Text('new page'), - ), - ), - ); - }, - ), - ); - await tester.pumpAndSettle(); - expect(find.text('new page'), findsOneWidget); - - nav.currentState!.maybePop(poppedResult); - await tester.pumpAndSettle(); - expect(receivedResult, poppedResult); - }, - variant: TargetPlatformVariant.all(), - ); - testWidgets('toggling canPop on secondary route allows/prevents backs', (WidgetTester tester) async { final GlobalKey nav = GlobalKey(); bool canPop = true; @@ -203,9 +115,9 @@ void main() { builder: (BuildContext context, StateSetter stateSetter) { oneContext = context; setState = stateSetter; - return PopScope( + return PopScope( canPop: canPop, - onPopInvokedWithResult: (bool didPop, Object? result) { + onPopInvoked: (bool didPop) { lastPopSuccess = didPop; }, child: const Center( @@ -359,7 +271,7 @@ void main() { if (!usePopScope) { return child; } - return const PopScope( + return const PopScope( canPop: false, child: child, ); @@ -402,12 +314,12 @@ void main() { return Column( children: [ if (usePopScope1) - const PopScope( + const PopScope( canPop: false, child: Text('hello'), ), if (usePopScope2) - const PopScope( + const PopScope( canPop: false, child: Text('hello'), ),