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 'framework.dart'; |
6 | import 'navigator.dart'; |
7 | import 'notification_listener.dart'; |
8 | import 'pop_scope.dart'; |
9 | |
10 | /// Enables the handling of system back gestures. |
11 | /// |
12 | /// Typically wraps a nested [Navigator] widget and allows it to handle system |
13 | /// back gestures in the [onPop] callback. |
14 | /// |
15 | /// {@tool dartpad} |
16 | /// This sample demonstrates how to use this widget to properly handle system |
17 | /// back gestures when using nested [Navigator]s. |
18 | /// |
19 | /// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.0.dart ** |
20 | /// {@end-tool} |
21 | /// |
22 | /// {@tool dartpad} |
23 | /// This sample demonstrates how to use this widget to properly handle system |
24 | /// back gestures with a bottom navigation bar whose tabs each have their own |
25 | /// nested [Navigator]s. |
26 | /// |
27 | /// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.1.dart ** |
28 | /// {@end-tool} |
29 | /// |
30 | /// See also: |
31 | /// |
32 | /// * [PopScope], which allows toggling the ability of a [Navigator] to |
33 | /// handle pops. |
34 | /// * [NavigationNotification], which indicates whether a [Navigator] in a |
35 | /// subtree can handle pops. |
36 | class NavigatorPopHandler extends StatefulWidget { |
37 | /// Creates an instance of [NavigatorPopHandler]. |
38 | const NavigatorPopHandler({ |
39 | super.key, |
40 | this.onPop, |
41 | this.enabled = true, |
42 | required this.child, |
43 | }); |
44 | |
45 | /// The widget to place below this in the widget tree. |
46 | /// |
47 | /// Typically this is a [Navigator] that will handle the pop when [onPop] is |
48 | /// called. |
49 | final Widget child; |
50 | |
51 | /// Whether this widget's ability to handle system back gestures is enabled or |
52 | /// disabled. |
53 | /// |
54 | /// When false, there will be no effect on system back gestures. If provided, |
55 | /// [onPop] will still be called. |
56 | /// |
57 | /// This can be used, for example, when the nested [Navigator] is no longer |
58 | /// active but remains in the widget tree, such as in an inactive tab. |
59 | /// |
60 | /// Defaults to true. |
61 | final bool enabled; |
62 | |
63 | /// Called when a handleable pop event happens. |
64 | /// |
65 | /// For example, a pop is handleable when a [Navigator] in [child] has |
66 | /// multiple routes on its stack. It's not handleable when it has only a |
67 | /// single route, and so [onPop] will not be called. |
68 | /// |
69 | /// Typically this is used to pop the [Navigator] in [child]. See the sample |
70 | /// code on [NavigatorPopHandler] for a full example of this. |
71 | final VoidCallback? onPop; |
72 | |
73 | @override |
74 | State<NavigatorPopHandler> createState() => _NavigatorPopHandlerState(); |
75 | } |
76 | |
77 | class _NavigatorPopHandlerState extends State<NavigatorPopHandler> { |
78 | bool _canPop = true; |
79 | |
80 | @override |
81 | Widget build(BuildContext context) { |
82 | // When the widget subtree indicates it can handle a pop, disable popping |
83 | // here, so that it can be manually handled in canPop. |
84 | return PopScope( |
85 | canPop: !widget.enabled || _canPop, |
86 | onPopInvoked: (bool didPop) { |
87 | if (didPop) { |
88 | return; |
89 | } |
90 | widget.onPop?.call(); |
91 | }, |
92 | // Listen to changes in the navigation stack in the widget subtree. |
93 | child: NotificationListener<NavigationNotification>( |
94 | onNotification: (NavigationNotification notification) { |
95 | // If this subtree cannot handle pop, then set canPop to true so |
96 | // that our PopScope will allow the Navigator higher in the tree to |
97 | // handle the pop instead. |
98 | final bool nextCanPop = !notification.canHandlePop; |
99 | if (nextCanPop != _canPop) { |
100 | setState(() { |
101 | _canPop = nextCanPop; |
102 | }); |
103 | } |
104 | return false; |
105 | }, |
106 | child: widget.child, |
107 | ), |
108 | ); |
109 | } |
110 | } |
111 | |