Skip to content

Commit 87cf346

Browse files
chunhtaipull[bot]
authored andcommitted
Reapply new PopScope API (flutter#147607)
same as previous but with soft transition on modalroute ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/wiki/Tree-hygiene#overview [Tree Hygiene]: https://github.com/flutter/flutter/wiki/Tree-hygiene [test-exempt]: https://github.com/flutter/flutter/wiki/Tree-hygiene#tests [Flutter Style Guide]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo [Features we expect every widget to implement]: https://github.com/flutter/flutter/wiki/Style-guide-for-Flutter-repo#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/wiki/Tree-hygiene#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/wiki/Chat [Data Driven Fixes]: https://github.com/flutter/flutter/wiki/Data-driven-Fixes
1 parent c6f8d1c commit 87cf346

File tree

18 files changed

+577
-81
lines changed

18 files changed

+577
-81
lines changed

dev/integration_tests/flutter_gallery/lib/demo/cupertino/cupertino_navigation_demo.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ class CupertinoNavigationDemo extends StatelessWidget {
4848

4949
@override
5050
Widget build(BuildContext context) {
51-
return PopScope(
51+
return PopScope<Object?>(
5252
// Prevent swipe popping of this page. Use explicit exit buttons only.
5353
canPop: false,
5454
child: DefaultTextStyle(

dev/integration_tests/flutter_gallery/lib/demo/material/full_screen_dialog_demo.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
110110
bool _hasName = false;
111111
late String _eventName;
112112

113-
Future<void> _handlePopInvoked(bool didPop) async {
113+
Future<void> _handlePopInvoked(bool didPop, Object? result) async {
114114
if (didPop) {
115115
return;
116116
}
@@ -175,7 +175,7 @@ class FullScreenDialogDemoState extends State<FullScreenDialogDemo> {
175175
),
176176
body: Form(
177177
canPop: !_saveNeeded && !_hasLocation && !_hasName,
178-
onPopInvoked: _handlePopInvoked,
178+
onPopInvokedWithResult: _handlePopInvoked,
179179
child: Scrollbar(
180180
child: ListView(
181181
primary: true,

dev/integration_tests/flutter_gallery/lib/demo/material/text_form_field_demo.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
143143
return null;
144144
}
145145

146-
Future<void> _handlePopInvoked(bool didPop) async {
146+
Future<void> _handlePopInvoked(bool didPop, Object? result) async {
147147
if (didPop) {
148148
return;
149149
}
@@ -192,7 +192,7 @@ class TextFormFieldDemoState extends State<TextFormFieldDemo> {
192192
key: _formKey,
193193
autovalidateMode: _autovalidateMode,
194194
canPop: _formKey.currentState == null || !_formWasEdited || _formKey.currentState!.validate(),
195-
onPopInvoked: _handlePopInvoked,
195+
onPopInvokedWithResult: _handlePopInvoked,
196196
child: Scrollbar(
197197
child: SingleChildScrollView(
198198
primary: true,

dev/integration_tests/flutter_gallery/lib/demo/shrine/expanding_bottom_sheet.dart

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -355,7 +355,7 @@ class ExpandingBottomSheetState extends State<ExpandingBottomSheet> with TickerP
355355

356356
// Closes the cart if the cart is open, otherwise exits the app (this should
357357
// only be relevant for Android).
358-
void _handlePopInvoked(bool didPop) {
358+
void _handlePopInvoked(bool didPop, Object? result) {
359359
if (didPop) {
360360
return;
361361
}
@@ -370,9 +370,9 @@ class ExpandingBottomSheetState extends State<ExpandingBottomSheet> with TickerP
370370
duration: const Duration(milliseconds: 225),
371371
curve: Curves.easeInOut,
372372
alignment: FractionalOffset.topLeft,
373-
child: PopScope(
373+
child: PopScope<Object?>(
374374
canPop: !_isOpen,
375-
onPopInvoked: _handlePopInvoked,
375+
onPopInvokedWithResult: _handlePopInvoked,
376376
child: AnimatedBuilder(
377377
animation: widget.hideController,
378378
builder: _buildSlideAnimation,

dev/integration_tests/flutter_gallery/lib/gallery/home.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -326,9 +326,9 @@ class _GalleryHomeState extends State<GalleryHome> with SingleTickerProviderStat
326326
backgroundColor: isDark ? _kFlutterBlue : theme.primaryColor,
327327
body: SafeArea(
328328
bottom: false,
329-
child: PopScope(
329+
child: PopScope<Object?>(
330330
canPop: _category == null,
331-
onPopInvoked: (bool didPop) {
331+
onPopInvokedWithResult: (bool didPop, Object? result) {
332332
if (didPop) {
333333
return;
334334
}

examples/api/lib/widgets/form/form.1.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,7 @@ class _SaveableFormState extends State<_SaveableForm> {
111111
const SizedBox(height: 20.0),
112112
Form(
113113
canPop: !_isDirty,
114-
onPopInvoked: (bool didPop) async {
114+
onPopInvokedWithResult: (bool didPop, Object? result) async {
115115
if (didPop) {
116116
return;
117117
}

examples/api/lib/widgets/pop_scope/pop_scope.0.dart

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,9 +109,9 @@ class _PageTwoState extends State<_PageTwo> {
109109
mainAxisAlignment: MainAxisAlignment.center,
110110
children: <Widget>[
111111
const Text('Page Two'),
112-
PopScope(
112+
PopScope<Object?>(
113113
canPop: false,
114-
onPopInvoked: (bool didPop) async {
114+
onPopInvokedWithResult: (bool didPop, Object? result) async {
115115
if (didPop) {
116116
return;
117117
}
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
// Copyright 2014 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
// This sample demonstrates how to use a PopScope to wrap a widget that
6+
// may pop the page with a result.
7+
8+
import 'package:flutter/material.dart';
9+
10+
void main() => runApp(const NavigatorPopHandlerApp());
11+
12+
class NavigatorPopHandlerApp extends StatelessWidget {
13+
const NavigatorPopHandlerApp({super.key});
14+
15+
@override
16+
Widget build(BuildContext context) {
17+
return MaterialApp(
18+
initialRoute: '/home',
19+
onGenerateRoute: (RouteSettings settings) {
20+
return switch (settings.name) {
21+
'/two' => MaterialPageRoute<FormData>(
22+
builder: (BuildContext context) => const _PageTwo(),
23+
),
24+
_ => MaterialPageRoute<void>(
25+
builder: (BuildContext context) => const _HomePage(),
26+
),
27+
};
28+
},
29+
);
30+
}
31+
}
32+
33+
class _HomePage extends StatefulWidget {
34+
const _HomePage();
35+
36+
@override
37+
State<_HomePage> createState() => _HomePageState();
38+
}
39+
40+
class _HomePageState extends State<_HomePage> {
41+
FormData? _formData;
42+
43+
@override
44+
Widget build(BuildContext context) {
45+
return Scaffold(
46+
body: Center(
47+
child: Column(
48+
mainAxisAlignment: MainAxisAlignment.center,
49+
children: <Widget>[
50+
const Text('Page One'),
51+
if (_formData != null)
52+
Text('Hello ${_formData!.name}, whose favorite food is ${_formData!.favoriteFood}.'),
53+
TextButton(
54+
onPressed: () async {
55+
final FormData formData =
56+
await Navigator.of(context).pushNamed<FormData?>('/two')
57+
?? const FormData();
58+
if (formData != _formData) {
59+
setState(() {
60+
_formData = formData;
61+
});
62+
}
63+
},
64+
child: const Text('Next page'),
65+
),
66+
],
67+
),
68+
),
69+
);
70+
}
71+
}
72+
73+
class _PopScopeWrapper extends StatelessWidget {
74+
const _PopScopeWrapper({required this.child});
75+
76+
final Widget child;
77+
78+
Future<bool?> _showBackDialog(BuildContext context) {
79+
return showDialog<bool>(
80+
context: context,
81+
builder: (BuildContext context) {
82+
return AlertDialog(
83+
title: const Text('Are you sure?'),
84+
content: const Text(
85+
'Are you sure you want to leave this page?',
86+
),
87+
actions: <Widget>[
88+
TextButton(
89+
style: TextButton.styleFrom(
90+
textStyle: Theme.of(context).textTheme.labelLarge,
91+
),
92+
child: const Text('Never mind'),
93+
onPressed: () {
94+
Navigator.pop(context, false);
95+
},
96+
),
97+
TextButton(
98+
style: TextButton.styleFrom(
99+
textStyle: Theme.of(context).textTheme.labelLarge,
100+
),
101+
child: const Text('Leave'),
102+
onPressed: () {
103+
Navigator.pop(context, true);
104+
},
105+
),
106+
],
107+
);
108+
},
109+
);
110+
}
111+
112+
@override
113+
Widget build(BuildContext context) {
114+
return PopScope<FormData>(
115+
canPop: false,
116+
// The result argument contains the pop result that is defined in `_PageTwo`.
117+
onPopInvokedWithResult: (bool didPop, FormData? result) async {
118+
if (didPop) {
119+
return;
120+
}
121+
final bool shouldPop = await _showBackDialog(context) ?? false;
122+
if (context.mounted && shouldPop) {
123+
Navigator.pop(context, result);
124+
}
125+
},
126+
child: child,
127+
);
128+
}
129+
}
130+
131+
// This is a PopScope wrapper over _PageTwoBody
132+
class _PageTwo extends StatelessWidget {
133+
const _PageTwo();
134+
135+
@override
136+
Widget build(BuildContext context) {
137+
return const _PopScopeWrapper(
138+
child: _PageTwoBody(),
139+
);
140+
}
141+
142+
}
143+
144+
class _PageTwoBody extends StatefulWidget {
145+
const _PageTwoBody();
146+
147+
@override
148+
State<_PageTwoBody> createState() => _PageTwoBodyState();
149+
}
150+
151+
class _PageTwoBodyState extends State<_PageTwoBody> {
152+
FormData _formData = const FormData();
153+
154+
@override
155+
Widget build(BuildContext context) {
156+
return Scaffold(
157+
body: Center(
158+
child: Column(
159+
mainAxisAlignment: MainAxisAlignment.center,
160+
children: <Widget>[
161+
const Text('Page Two'),
162+
Form(
163+
child: Column(
164+
children: <Widget>[
165+
TextFormField(
166+
decoration: const InputDecoration(
167+
hintText: 'Enter your name.',
168+
),
169+
onChanged: (String value) {
170+
_formData = _formData.copyWith(
171+
name: value,
172+
);
173+
},
174+
),
175+
TextFormField(
176+
decoration: const InputDecoration(
177+
hintText: 'Enter your favorite food.',
178+
),
179+
onChanged: (String value) {
180+
_formData = _formData.copyWith(
181+
favoriteFood: value,
182+
);
183+
},
184+
),
185+
],
186+
),
187+
),
188+
TextButton(
189+
onPressed: () async {
190+
Navigator.maybePop(context, _formData);
191+
},
192+
child: const Text('Go back'),
193+
),
194+
],
195+
),
196+
),
197+
);
198+
}
199+
}
200+
201+
@immutable
202+
class FormData {
203+
const FormData({
204+
this.name = '',
205+
this.favoriteFood = '',
206+
});
207+
208+
final String name;
209+
final String favoriteFood;
210+
211+
FormData copyWith({String? name, String? favoriteFood}) {
212+
return FormData(
213+
name: name ?? this.name,
214+
favoriteFood: favoriteFood ?? this.favoriteFood,
215+
);
216+
}
217+
218+
@override
219+
bool operator ==(Object other) {
220+
if (identical(this, other)) {
221+
return true;
222+
}
223+
if (other.runtimeType != runtimeType) {
224+
return false;
225+
}
226+
return other is FormData
227+
&& other.name == name
228+
&& other.favoriteFood == favoriteFood;
229+
}
230+
231+
@override
232+
int get hashCode => Object.hash(name, favoriteFood);
233+
}

0 commit comments

Comments
 (0)