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
5import 'framework.dart';
6import 'navigator.dart';
7import 'notification_listener.dart';
8import '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.
36class 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
77class _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