blob: 203a85beded186327c0708b6789e2f8565c636ab [file] [log] [blame]
// Copyright 2014 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'framework.dart';
import 'navigator.dart';
import 'notification_listener.dart';
import 'pop_scope.dart';
/// Enables the handling of system back gestures.
///
/// Typically wraps a nested [Navigator] widget and allows it to handle system
/// back gestures in the [onPop] callback.
///
/// {@tool dartpad}
/// This sample demonstrates how to use this widget to properly handle system
/// back gestures when using nested [Navigator]s.
///
/// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.0.dart **
/// {@end-tool}
///
/// {@tool dartpad}
/// This sample demonstrates how to use this widget to properly handle system
/// back gestures with a bottom navigation bar whose tabs each have their own
/// nested [Navigator]s.
///
/// ** See code in examples/api/lib/widgets/navigator_pop_handler/navigator_pop_handler.1.dart **
/// {@end-tool}
///
/// See also:
///
/// * [PopScope], which allows toggling the ability of a [Navigator] to
/// handle pops.
/// * [NavigationNotification], which indicates whether a [Navigator] in a
/// subtree can handle pops.
class NavigatorPopHandler extends StatefulWidget {
/// Creates an instance of [NavigatorPopHandler].
const NavigatorPopHandler({
super.key,
this.onPop,
this.enabled = true,
required this.child,
});
/// The widget to place below this in the widget tree.
///
/// Typically this is a [Navigator] that will handle the pop when [onPop] is
/// called.
final Widget child;
/// Whether this widget's ability to handle system back gestures is enabled or
/// disabled.
///
/// When false, there will be no effect on system back gestures. If provided,
/// [onPop] will still be called.
///
/// This can be used, for example, when the nested [Navigator] is no longer
/// active but remains in the widget tree, such as in an inactive tab.
///
/// Defaults to true.
final bool enabled;
/// Called when a handleable pop event happens.
///
/// For example, a pop is handleable when a [Navigator] in [child] has
/// multiple routes on its stack. It's not handleable when it has only a
/// single route, and so [onPop] will not be called.
///
/// Typically this is used to pop the [Navigator] in [child]. See the sample
/// code on [NavigatorPopHandler] for a full example of this.
final VoidCallback? onPop;
@override
State<NavigatorPopHandler> createState() => _NavigatorPopHandlerState();
}
class _NavigatorPopHandlerState extends State<NavigatorPopHandler> {
bool _canPop = true;
@override
Widget build(BuildContext context) {
// When the widget subtree indicates it can handle a pop, disable popping
// here, so that it can be manually handled in canPop.
return PopScope(
canPop: !widget.enabled || _canPop,
onPopInvoked: (bool didPop) {
if (didPop) {
return;
}
widget.onPop?.call();
},
// Listen to changes in the navigation stack in the widget subtree.
child: NotificationListener<NavigationNotification>(
onNotification: (NavigationNotification notification) {
// If this subtree cannot handle pop, then set canPop to true so
// that our PopScope will allow the Navigator higher in the tree to
// handle the pop instead.
final bool nextCanPop = !notification.canHandlePop;
if (nextCanPop != _canPop) {
setState(() {
_canPop = nextCanPop;
});
}
return false;
},
child: widget.child,
),
);
}
}