Add generic type for result in PopScope (#139164)

Adds a generic type and pop result to popscope and its friend.

The use cases are to be able to capture the result when the pop is called.

migration guide: https://github.com/flutter/website/pull/9872
This commit is contained in:
chunhtai 2024-04-18 12:30:10 -07:00 committed by GitHub
parent ffa3b303ee
commit 7ee05a05ff
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 466 additions and 55 deletions

View File

@ -48,7 +48,7 @@ class CupertinoNavigationDemo extends StatelessWidget {
@override
Widget build(BuildContext context) {
return PopScope(
return PopScope<Object?>(
// Prevent swipe popping of this page. Use explicit exit buttons only.
canPop: false,
child: DefaultTextStyle(

View File

@ -110,7 +110,7 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
bool _hasName = false;
late String _eventName;
Future<void> _handlePopInvoked(bool didPop) async {
Future<void> _handlePopInvoked(bool didPop, Object? result) async {
if (didPop) {
return;
}

View File

@ -143,7 +143,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
return null;
}
Future<void> _handlePopInvoked(bool didPop) async {
Future<void> _handlePopInvoked(bool didPop, Object? result) async {
if (didPop) {
return;
}

View File

@ -355,7 +355,7 @@ class ExpandingBottomSheetState extends State<ExpandingBottomSheet> 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) {
void _handlePopInvoked(bool didPop, Object? result) {
if (didPop) {
return;
}
@ -370,7 +370,7 @@ class ExpandingBottomSheetState extends State<ExpandingBottomSheet> with TickerP
duration: const Duration(milliseconds: 225),
curve: Curves.easeInOut,
alignment: FractionalOffset.topLeft,
child: PopScope(
child: PopScope<Object?>(
canPop: !_isOpen,
onPopInvoked: _handlePopInvoked,
child: AnimatedBuilder(

View File

@ -326,9 +326,9 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat
backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor,
body: SafeArea(
bottom: false,
child: PopScope(
child: PopScope<Object?>(
canPop: _category == null,
onPopInvoked: (bool didPop) {
onPopInvoked: (bool didPop, Object? result) {
if (didPop) {
return;
}

View File

@ -111,7 +111,7 @@ class _SaveableFormState extends State<_SaveableForm> {
const SizedBox(height: 20.0),
Form(
canPop: !_isDirty,
onPopInvoked: (bool didPop) async {
onPopInvoked: (bool didPop, Object? result) async {
if (didPop) {
return;
}

View File

@ -109,9 +109,9 @@ class _PageTwoState extends State<_PageTwo> {
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
const Text('Page Two'),
PopScope(
PopScope<Object?>(
canPop: false,
onPopInvoked: (bool didPop) async {
onPopInvoked: (bool didPop, Object? result) async {
if (didPop) {
return;
}

View File

@ -0,0 +1,232 @@
// 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 how to use PopScope to wrap 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<FormData>(
builder: (BuildContext context) => const _PageTwo(),
),
_ => MaterialPageRoute<void>(
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: <Widget>[
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<FormData?>('/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<bool?> _showBackDialog(BuildContext context) {
return showDialog<bool>(
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: <Widget>[
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<FormData>(
canPop: false,
// The result contains pop result in `_PageTwo`.
onPopInvoked: (bool didPop, FormData? result) async {
if (didPop) {
return;
}
final bool shouldPop = await _showBackDialog(context) ?? false;
if (context.mounted && shouldPop) {
Navigator.pop(context, result);
}
},
child: const _PageTwoBody(),
);
}
}
// 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: <Widget>[
const Text('Page Two'),
Form(
child: Column(
children: <Widget>[
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);
}

View File

@ -0,0 +1,67 @@
// 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);
});
}

View File

@ -1230,9 +1230,9 @@ class _MasterDetailFlowState extends State<_MasterDetailFlow> implements _PageOp
}
MaterialPageRoute<void> _detailPageRoute(Object? arguments) {
return MaterialPageRoute<dynamic>(builder: (BuildContext context) {
return PopScope(
onPopInvoked: (bool didPop) {
return MaterialPageRoute<void>(builder: (BuildContext context) {
return PopScope<void>(
onPopInvoked: (bool didPop, void result) {
// No need for setState() as rebuild happens on navigation pop.
focus = _Focus.master;
},

View File

@ -177,7 +177,7 @@ class Form extends StatefulWidget {
/// * [canPop], which also comes from [PopScope] and is often used in
/// conjunction with this parameter.
/// * [PopScope.onPopInvoked], which is what [Form] delegates to internally.
final PopInvokedCallback? onPopInvoked;
final PopInvokedCallback<Object?>? onPopInvoked;
/// Called when one of the form fields changes.
///
@ -244,7 +244,7 @@ class FormState extends State<Form> {
}
if (widget.canPop != null || widget.onPopInvoked != null) {
return PopScope(
return PopScope<Object?>(
canPop: widget.canPop ?? true,
onPopInvoked: widget.onPopInvoked,
child: _FormScope(

View File

@ -355,7 +355,7 @@ 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 onPopInvoked(bool didPop) {}
void onPopInvoked(bool didPop, T? result) {}
/// 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);
route.onPopInvoked(true, result);
}
bool _reportRemovalToObserver = true;
@ -5239,7 +5239,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
pop(result);
return true;
case RoutePopDisposition.doNotPop:
lastEntry.route.onPopInvoked(false);
lastEntry.route.onPopInvoked(false, result);
return true;
}
}
@ -5282,7 +5282,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
assert(entry.route._popCompleter.isCompleted);
entry.currentState = _RouteLifecycle.pop;
}
entry.route.onPopInvoked(true);
entry.route.onPopInvoked(true, result);
} else {
entry.pop<T>(result);
assert (entry.currentState == _RouteLifecycle.pop);

View File

@ -81,9 +81,9 @@ class _NavigatorPopHandlerState extends State<NavigatorPopHandler> {
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<Object?>(
canPop: !widget.enabled || _canPop,
onPopInvoked: (bool didPop) {
onPopInvoked: (bool didPop, Object? result) {
if (didPop) {
return;
}

View File

@ -10,10 +10,15 @@ import 'routes.dart';
/// Manages back navigation gestures.
///
/// The generic type should match or be supertype of the generic type of the
/// enclosing [Route]. If the enclosing Route is a `MaterialPageRoute<int>`,
/// you can define [PopScope] with int or any supertype of int.
///
/// The [canPop] parameter disables back gestures when set to `false`.
///
/// The [onPopInvoked] parameter reports when pop navigation was attempted, and
/// `didPop` indicates whether or not the navigation was successful.
/// `didPop` indicates whether or not the navigation was successful. The
/// `result` contains the pop result.
///
/// 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.
@ -41,6 +46,13 @@ import 'routes.dart';
/// ** 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
@ -49,7 +61,7 @@ import 'routes.dart';
/// 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<T> extends StatefulWidget {
/// Creates a widget that registers a callback to veto attempts by the user to
/// dismiss the enclosing [ModalRoute].
const PopScope({
@ -78,10 +90,12 @@ 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 PopInvokedCallback? onPopInvoked;
final PopInvokedCallback<T>? onPopInvoked;
/// {@template flutter.widgets.PopScope.canPop}
/// When false, blocks the current route from being popped.
@ -99,14 +113,16 @@ class PopScope extends StatefulWidget {
final bool canPop;
@override
State<PopScope> createState() => _PopScopeState();
State<PopScope<T>> createState() => _PopScopeState<T>();
}
class _PopScopeState extends State<PopScope> implements PopEntry {
class _PopScopeState<T> extends State<PopScope<T>> implements PopEntry<T> {
ModalRoute<dynamic>? _route;
@override
PopInvokedCallback? get onPopInvoked => widget.onPopInvoked;
void onPopInvoked(bool didPop, T? result) {
widget.onPopInvoked?.call(didPop, result);
}
@override
late final ValueNotifier<bool> canPopNotifier;
@ -129,7 +145,7 @@ class _PopScopeState extends State<PopScope> implements PopEntry {
}
@override
void didUpdateWidget(PopScope oldWidget) {
void didUpdateWidget(PopScope<T> oldWidget) {
super.didUpdateWidget(oldWidget);
canPopNotifier.value = widget.canPop;
}

View File

@ -1669,7 +1669,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
final List<WillPopCallback> _willPopCallbacks = <WillPopCallback>[];
final Set<PopEntry> _popEntries = <PopEntry>{};
// Holding as Object? instead of T so that PopScope in this route can be
// declared with any supertype of T.
final Set<PopEntry<Object?>> _popEntries = <PopEntry<Object?>>{};
/// Returns [RoutePopDisposition.doNotPop] if any of callbacks added with
/// [addScopedWillPopCallback] returns either false or null. If they all
@ -1724,7 +1726,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// method checks.
@override
RoutePopDisposition get popDisposition {
for (final PopEntry popEntry in _popEntries) {
for (final PopEntry<Object?> popEntry in _popEntries) {
if (!popEntry.canPopNotifier.value) {
return RoutePopDisposition.doNotPop;
}
@ -1734,9 +1736,9 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
}
@override
void onPopInvoked(bool didPop) {
for (final PopEntry popEntry in _popEntries) {
popEntry.onPopInvoked?.call(didPop);
void onPopInvoked(bool didPop, T? result) {
for (final PopEntry<Object?> popEntry in _popEntries) {
popEntry.onPopInvoked(didPop, result);
}
}
@ -1793,7 +1795,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// See also:
///
/// * [unregisterPopEntry], which performs the opposite operation.
void registerPopEntry(PopEntry popEntry) {
void registerPopEntry(PopEntry<Object?> popEntry) {
_popEntries.add(popEntry);
popEntry.canPopNotifier.addListener(_handlePopEntryChange);
_handlePopEntryChange();
@ -1804,7 +1806,7 @@ abstract class ModalRoute<T> extends TransitionRoute<T> with LocalHistoryRoute<T
/// See also:
///
/// * [registerPopEntry], which performs the opposite operation.
void unregisterPopEntry(PopEntry popEntry) {
void unregisterPopEntry(PopEntry<Object?> popEntry) {
_popEntries.remove(popEntry);
popEntry.canPopNotifier.removeListener(_handlePopEntryChange);
_handlePopEntryChange();
@ -2413,7 +2415,9 @@ typedef RouteTransitionsBuilder = Widget Function(BuildContext context, Animatio
///
/// Accepts a didPop boolean indicating whether or not back navigation
/// succeeded.
typedef PopInvokedCallback = void Function(bool didPop);
///
/// The `result` contains the pop result.
typedef PopInvokedCallback<T> = void Function(bool didPop, T? result);
/// Allows listening to and preventing pops.
///
@ -2425,9 +2429,13 @@ typedef PopInvokedCallback = void Function(bool didPop);
/// * [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 {
abstract class PopEntry<T> {
/// {@macro flutter.widgets.PopScope.onPopInvoked}
PopInvokedCallback? get onPopInvoked;
// This can't be a function getter since dart vm doesn't allow upcasting
// generic type of the function getter. This prevents customers from declaring
// PopScope with any generic type that is subtype of ModalRoute._popEntries.
// See https://github.com/dart-lang/sdk/issues/55427.
void onPopInvoked(bool didPop, T? result);
/// {@macro flutter.widgets.PopScope.canPop}
ValueListenable<bool> get canPopNotifier;

View File

@ -305,7 +305,7 @@ void main() {
BottomNavigationBarItem(label: '', icon: Text('2'))
],
),
tabBuilder: (_, int i) => PopScope(
tabBuilder: (_, int i) => PopScope<Object?>(
canPop: false,
child: CupertinoTabView(
navigatorKey: key,

View File

@ -2989,7 +2989,7 @@ void main() {
const List<Page<void>> myPages = <Page<void>>[
MaterialPage<void>(child: Text('page1')),
MaterialPage<void>(
child: PopScope(
child: PopScope<void>(
canPop: false,
child: Text('page2'),
),
@ -4908,9 +4908,9 @@ void main() {
home: StatefulBuilder(
builder: (BuildContext context, StateSetter setState) {
builderSetState = setState;
return PopScope(
return PopScope<Object?>(
canPop: canPop(),
onPopInvoked: (bool success) {
onPopInvoked: (bool success, Object? result) {
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<Object?>(
canPop: canPop(),
onPopInvoked: (bool success) {
onPopInvoked: (bool success, Object? result) {
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<Object?>(
canPop: canPop(),
onPopInvoked: (bool success) {
onPopInvoked: (bool success, Object? result) {
if (success || pages.last == _PageWithYesPop.noPop) {
return;
}
@ -5189,7 +5189,7 @@ void main() {
child: _LinksPage(
title: 'Can pop page',
canPop: true,
onPopInvoked: (bool didPop) {
onPopInvoked: (bool didPop, void result) {
onPopInvokedCallCount += 1;
},
),
@ -5556,7 +5556,7 @@ class _LinksPage extends StatelessWidget {
final bool? canPop;
final VoidCallback? onBack;
final String title;
final PopInvokedCallback? onPopInvoked;
final PopInvokedCallback<Object?>? onPopInvoked;
@override
Widget build(BuildContext context) {
@ -5575,7 +5575,7 @@ class _LinksPage extends StatelessWidget {
child: const Text('Go back'),
),
if (canPop != null)
PopScope(
PopScope<void>(
canPop: canPop!,
onPopInvoked: onPopInvoked,
child: const SizedBox.shrink(),

View File

@ -47,7 +47,7 @@ void main() {
builder: (BuildContext buildContext, StateSetter stateSetter) {
context = buildContext;
setState = stateSetter;
return PopScope(
return PopScope<Object?>(
canPop: canPop,
child: const Center(
child: Column(
@ -79,6 +79,94 @@ void main() {
variant: TargetPlatformVariant.all(),
);
testWidgets('pop scope can receive result', (WidgetTester tester) async {
Object? receivedResult;
final Object poppedResult = Object();
final GlobalKey<NavigatorState> nav = GlobalKey<NavigatorState>();
await tester.pumpWidget(
MaterialApp(
initialRoute: '/',
navigatorKey: nav,
home: Scaffold(
body: PopScope<Object?>(
canPop: false,
onPopInvoked: (bool didPop, Object? result) {
receivedResult = result;
},
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
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<NavigatorState> nav = GlobalKey<NavigatorState>();
await tester.pumpWidget(
MaterialApp(
initialRoute: '/',
navigatorKey: nav,
home: Scaffold(
body: PopScope<Object?>(
canPop: false,
onPopInvoked: (bool didPop, Object? result) {
receivedResult = result;
},
child: const Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text('Home/PopScope Page'),
],
),
),
),
),
),
);
nav.currentState!.push(
MaterialPageRoute<int>(
builder: (BuildContext context) {
return Scaffold(
body: PopScope<Object?>(
canPop: false,
onPopInvoked: (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<NavigatorState> nav = GlobalKey<NavigatorState>();
bool canPop = true;
@ -115,9 +203,9 @@ void main() {
builder: (BuildContext context, StateSetter stateSetter) {
oneContext = context;
setState = stateSetter;
return PopScope(
return PopScope<Object?>(
canPop: canPop,
onPopInvoked: (bool didPop) {
onPopInvoked: (bool didPop, Object? result) {
lastPopSuccess = didPop;
},
child: const Center(
@ -271,7 +359,7 @@ void main() {
if (!usePopScope) {
return child;
}
return const PopScope(
return const PopScope<Object?>(
canPop: false,
child: child,
);
@ -314,12 +402,12 @@ void main() {
return Column(
children: <Widget>[
if (usePopScope1)
const PopScope(
const PopScope<Object?>(
canPop: false,
child: Text('hello'),
),
if (usePopScope2)
const PopScope(
const PopScope<Object?>(
canPop: false,
child: Text('hello'),
),