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 | /// @docImport 'package:flutter/cupertino.dart'; |
6 | /// |
7 | /// @docImport 'form.dart'; |
8 | /// @docImport 'navigator_pop_handler.dart'; |
9 | library; |
10 | |
11 | import 'package:flutter/foundation.dart'; |
12 | |
13 | import 'framework.dart'; |
14 | import 'navigator.dart'; |
15 | import 'routes.dart'; |
16 | |
17 | /// A callback type for informing that a navigation pop has been invoked, |
18 | /// whether or not it was handled successfully. |
19 | /// |
20 | /// Accepts a didPop boolean indicating whether or not back navigation |
21 | /// succeeded. |
22 | @Deprecated( |
23 | 'Use PopWithResultInvokedCallback instead. ' |
24 | 'This feature was deprecated after v3.22.0-12.0.pre.' , |
25 | ) |
26 | typedef PopInvokedCallback = void Function(bool didPop); |
27 | |
28 | /// Manages back navigation gestures. |
29 | /// |
30 | /// The generic type should match or be a supertype of the generic type of the |
31 | /// enclosing [Route]. For example, if the enclosing Route is a |
32 | /// `MaterialPageRoute<int>`, you can define [PopScope] with `int` or any |
33 | /// supertype of `int`. |
34 | /// |
35 | /// The [canPop] parameter disables back gestures when set to `false`. |
36 | /// |
37 | /// The [onPopInvokedWithResult] parameter reports when pop navigation was attempted, and |
38 | /// `didPop` indicates whether or not the navigation was successful. The |
39 | /// `result` contains the pop result. |
40 | /// |
41 | /// Android has a system back gesture that is a swipe inward from near the edge |
42 | /// of the screen. It is recognized by Android before being passed to Flutter. |
43 | /// iOS has a similar gesture that is recognized in Flutter by |
44 | /// [CupertinoRouteTransitionMixin], not by iOS, and is therefore not a system |
45 | /// back gesture. |
46 | /// |
47 | /// If [canPop] is false, then a system back gesture will not pop the route off |
48 | /// of the enclosing [Navigator]. [onPopInvokedWithResult] will still be called, and |
49 | /// `didPop` will be `false`. On iOS when using [CupertinoRouteTransitionMixin] |
50 | /// with [canPop] set to false, no gesture will be detected at all, so |
51 | /// [onPopInvokedWithResult] will not be called. Programmatically attempting pop |
52 | /// navigation will also result in a call to [onPopInvokedWithResult], with `didPop` |
53 | /// indicating success or failure. |
54 | /// |
55 | /// If [canPop] is true, then a system back gesture will cause the enclosing |
56 | /// [Navigator] to receive a pop as usual. [onPopInvokedWithResult] will be called with |
57 | /// `didPop` as true, unless the pop failed for reasons unrelated to |
58 | /// [PopScope], in which case it will be false. |
59 | /// |
60 | /// {@tool dartpad} |
61 | /// This sample demonstrates showing a confirmation dialog before navigating |
62 | /// away from a page. |
63 | /// |
64 | /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.0.dart ** |
65 | /// {@end-tool} |
66 | /// |
67 | /// {@tool dartpad} |
68 | /// This sample demonstrates showing how to use PopScope to wrap widget that |
69 | /// may pop the page with a result. |
70 | /// |
71 | /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.1.dart ** |
72 | /// {@end-tool} |
73 | /// |
74 | /// See also: |
75 | /// |
76 | /// * [NavigatorPopHandler], which is a less verbose way to handle system back |
77 | /// gestures in simple cases of nested [Navigator]s. |
78 | /// * [Form.canPop] and [Form.onPopInvokedWithResult], which can be used to handle system |
79 | /// back gestures in the case of a form with unsaved data. |
80 | /// * [ModalRoute.registerPopEntry] and [ModalRoute.unregisterPopEntry], |
81 | /// which this widget uses to integrate with Flutter's navigation system. |
82 | @optionalTypeArgs |
83 | class PopScope<T> extends StatefulWidget { |
84 | /// Creates a widget that registers a callback to veto attempts by the user to |
85 | /// dismiss the enclosing [ModalRoute]. |
86 | const PopScope({ |
87 | super.key, |
88 | required this.child, |
89 | this.canPop = true, |
90 | this.onPopInvokedWithResult, |
91 | @Deprecated( |
92 | 'Use onPopInvokedWithResult instead. ' |
93 | 'This feature was deprecated after v3.22.0-12.0.pre.' , |
94 | ) |
95 | this.onPopInvoked, |
96 | }) : assert(onPopInvokedWithResult == null || onPopInvoked == null, 'onPopInvoked is deprecated, use onPopInvokedWithResult' ); |
97 | |
98 | /// The widget below this widget in the tree. |
99 | /// |
100 | /// {@macro flutter.widgets.ProxyWidget.child} |
101 | final Widget child; |
102 | |
103 | /// {@template flutter.widgets.PopScope.onPopInvokedWithResult} |
104 | /// Called after a route pop was handled. |
105 | /// {@endtemplate} |
106 | /// |
107 | /// It's not possible to prevent the pop from happening at the time that this |
108 | /// method is called; the pop has already happened. Use [canPop] to |
109 | /// disable pops in advance. |
110 | /// |
111 | /// This will still be called even when the pop is canceled. A pop is canceled |
112 | /// when the relevant [Route.popDisposition] returns false, such as when |
113 | /// [canPop] is set to false on a [PopScope]. The `didPop` parameter |
114 | /// indicates whether or not the back navigation actually happened |
115 | /// successfully. |
116 | /// |
117 | /// The `result` contains the pop result. |
118 | /// |
119 | /// See also: |
120 | /// |
121 | /// * [Route.onPopInvokedWithResult], which is similar. |
122 | final PopInvokedWithResultCallback<T>? onPopInvokedWithResult; |
123 | |
124 | /// Called after a route pop was handled. |
125 | /// |
126 | /// It's not possible to prevent the pop from happening at the time that this |
127 | /// method is called; the pop has already happened. Use [canPop] to |
128 | /// disable pops in advance. |
129 | /// |
130 | /// This will still be called even when the pop is canceled. A pop is canceled |
131 | /// when the relevant [Route.popDisposition] returns false, such as when |
132 | /// [canPop] is set to false on a [PopScope]. The `didPop` parameter |
133 | /// indicates whether or not the back navigation actually happened |
134 | /// successfully. |
135 | @Deprecated( |
136 | 'Use onPopInvokedWithResult instead. ' |
137 | 'This feature was deprecated after v3.22.0-12.0.pre.' , |
138 | ) |
139 | final PopInvokedCallback? onPopInvoked; |
140 | |
141 | void _callPopInvoked(bool didPop, T? result) { |
142 | if (onPopInvokedWithResult != null) { |
143 | onPopInvokedWithResult!(didPop, result); |
144 | return; |
145 | } |
146 | onPopInvoked?.call(didPop); |
147 | } |
148 | |
149 | /// {@template flutter.widgets.PopScope.canPop} |
150 | /// When false, blocks the current route from being popped. |
151 | /// |
152 | /// This includes the root route, where upon popping, the Flutter app would |
153 | /// exit. |
154 | /// |
155 | /// If multiple [PopScope] widgets appear in a route's widget subtree, then |
156 | /// each and every `canPop` must be `true` in order for the route to be |
157 | /// able to pop. |
158 | /// |
159 | /// [Android's predictive back](https://developer.android.com/guide/navigation/predictive-back-gesture) |
160 | /// feature will not animate when this boolean is false. |
161 | /// {@endtemplate} |
162 | final bool canPop; |
163 | |
164 | @override |
165 | State<PopScope<T>> createState() => _PopScopeState<T>(); |
166 | } |
167 | |
168 | class _PopScopeState<T> extends State<PopScope<T>> implements PopEntry<T> { |
169 | ModalRoute<dynamic>? _route; |
170 | |
171 | @override |
172 | void onPopInvoked(bool didPop) { |
173 | throw UnimplementedError(); |
174 | } |
175 | |
176 | @override |
177 | void onPopInvokedWithResult(bool didPop, T? result) { |
178 | widget._callPopInvoked(didPop, result); |
179 | } |
180 | |
181 | @override |
182 | late final ValueNotifier<bool> canPopNotifier; |
183 | |
184 | @override |
185 | void initState() { |
186 | super.initState(); |
187 | canPopNotifier = ValueNotifier<bool>(widget.canPop); |
188 | } |
189 | |
190 | @override |
191 | void didChangeDependencies() { |
192 | super.didChangeDependencies(); |
193 | final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context); |
194 | if (nextRoute != _route) { |
195 | _route?.unregisterPopEntry(this); |
196 | _route = nextRoute; |
197 | _route?.registerPopEntry(this); |
198 | } |
199 | } |
200 | |
201 | @override |
202 | void didUpdateWidget(PopScope<T> oldWidget) { |
203 | super.didUpdateWidget(oldWidget); |
204 | canPopNotifier.value = widget.canPop; |
205 | } |
206 | |
207 | @override |
208 | void dispose() { |
209 | _route?.unregisterPopEntry(this); |
210 | canPopNotifier.dispose(); |
211 | super.dispose(); |
212 | } |
213 | |
214 | @override |
215 | Widget build(BuildContext context) => widget.child; |
216 | } |
217 | |