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 | import 'package:flutter/foundation.dart'; |
6 | |
7 | import 'framework.dart'; |
8 | import 'navigator.dart'; |
9 | import 'routes.dart'; |
10 | |
11 | /// Manages system back gestures. |
12 | /// |
13 | /// The [canPop] parameter can be used to disable system back gestures. Defaults |
14 | /// to true, meaning that back gestures happen as usual. |
15 | /// |
16 | /// The [onPopInvoked] parameter reports when system back gestures occur, |
17 | /// regardless of whether or not they were successful. |
18 | /// |
19 | /// If [canPop] is false, then a system back gesture will not pop the route off |
20 | /// of the enclosing [Navigator]. [onPopInvoked] will still be called, and |
21 | /// `didPop` will be `false`. |
22 | /// |
23 | /// If [canPop] is true, then a system back gesture will cause the enclosing |
24 | /// [Navigator] to receive a pop as usual. [onPopInvoked] will be called with |
25 | /// `didPop` as `true`, unless the pop failed for reasons unrelated to |
26 | /// [PopScope], in which case it will be `false`. |
27 | /// |
28 | /// {@tool dartpad} |
29 | /// This sample demonstrates how to use this widget to handle nested navigation |
30 | /// in a bottom navigation bar. |
31 | /// |
32 | /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.0.dart ** |
33 | /// {@end-tool} |
34 | /// |
35 | /// See also: |
36 | /// |
37 | /// * [NavigatorPopHandler], which is a less verbose way to handle system back |
38 | /// gestures in simple cases of nested [Navigator]s. |
39 | /// * [Form.canPop] and [Form.onPopInvoked], which can be used to handle system |
40 | /// back gestures in the case of a form with unsaved data. |
41 | /// * [ModalRoute.registerPopEntry] and [ModalRoute.unregisterPopEntry], |
42 | /// which this widget uses to integrate with Flutter's navigation system. |
43 | class PopScope extends StatefulWidget { |
44 | /// Creates a widget that registers a callback to veto attempts by the user to |
45 | /// dismiss the enclosing [ModalRoute]. |
46 | const PopScope({ |
47 | super.key, |
48 | required this.child, |
49 | this.canPop = true, |
50 | this.onPopInvoked, |
51 | }); |
52 | |
53 | /// The widget below this widget in the tree. |
54 | /// |
55 | /// {@macro flutter.widgets.ProxyWidget.child} |
56 | final Widget child; |
57 | |
58 | /// {@template flutter.widgets.PopScope.onPopInvoked} |
59 | /// Called after a route pop was handled. |
60 | /// {@endtemplate} |
61 | /// |
62 | /// It's not possible to prevent the pop from happening at the time that this |
63 | /// method is called; the pop has already happened. Use [canPop] to |
64 | /// disable pops in advance. |
65 | /// |
66 | /// This will still be called even when the pop is canceled. A pop is canceled |
67 | /// when the relevant [Route.popDisposition] returns false, such as when |
68 | /// [canPop] is set to false on a [PopScope]. The `didPop` parameter |
69 | /// indicates whether or not the back navigation actually happened |
70 | /// successfully. |
71 | /// |
72 | /// See also: |
73 | /// |
74 | /// * [Route.onPopInvoked], which is similar. |
75 | final PopInvokedCallback? onPopInvoked; |
76 | |
77 | /// {@template flutter.widgets.PopScope.canPop} |
78 | /// When false, blocks the current route from being popped. |
79 | /// |
80 | /// This includes the root route, where upon popping, the Flutter app would |
81 | /// exit. |
82 | /// |
83 | /// If multiple [PopScope] widgets appear in a route's widget subtree, then |
84 | /// each and every `canPop` must be `true` in order for the route to be |
85 | /// able to pop. |
86 | /// |
87 | /// [Android's predictive back](https://developer.android.com/guide/navigation/predictive-back-gesture) |
88 | /// feature will not animate when this boolean is false. |
89 | /// {@endtemplate} |
90 | final bool canPop; |
91 | |
92 | @override |
93 | State<PopScope> createState() => _PopScopeState(); |
94 | } |
95 | |
96 | class _PopScopeState extends State<PopScope> implements PopEntry { |
97 | ModalRoute<dynamic>? _route; |
98 | |
99 | @override |
100 | PopInvokedCallback? get onPopInvoked => widget.onPopInvoked; |
101 | |
102 | @override |
103 | late final ValueNotifier<bool> canPopNotifier; |
104 | |
105 | @override |
106 | void initState() { |
107 | super.initState(); |
108 | canPopNotifier = ValueNotifier<bool>(widget.canPop); |
109 | } |
110 | |
111 | @override |
112 | void didChangeDependencies() { |
113 | super.didChangeDependencies(); |
114 | final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context); |
115 | if (nextRoute != _route) { |
116 | _route?.unregisterPopEntry(this); |
117 | _route = nextRoute; |
118 | _route?.registerPopEntry(this); |
119 | } |
120 | } |
121 | |
122 | @override |
123 | void didUpdateWidget(PopScope oldWidget) { |
124 | super.didUpdateWidget(oldWidget); |
125 | canPopNotifier.value = widget.canPop; |
126 | } |
127 | |
128 | @override |
129 | void dispose() { |
130 | _route?.unregisterPopEntry(this); |
131 | canPopNotifier.dispose(); |
132 | super.dispose(); |
133 | } |
134 | |
135 | @override |
136 | Widget build(BuildContext context) => widget.child; |
137 | } |
138 | |