// 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 'package:flutter/foundation.dart'; import 'framework.dart'; import 'navigator.dart'; import 'routes.dart'; /// Manages back navigation gestures. /// /// The [canPop] parameter disables back gestures when set to `false`. /// /// The [onPopInvoked] parameter reports when pop navigation was attempted, and /// `didPop` indicates whether or not the navigation was successful. /// /// Android has a system back gesture that is a swipe inward from near the edge /// of the screen. It is recognized by Android before being passed to Flutter. /// iOS has a similar gesture that is recognized in Flutter by /// [CupertinoRouteTransitionMixin], not by iOS, and is therefore not a system /// back gesture. /// /// If [canPop] is false, then a system back gesture will not pop the route off /// of the enclosing [Navigator]. [onPopInvoked] will still be called, and /// `didPop` will be `false`. On iOS when using [CupertinoRouteTransitionMixin] /// with [canPop] set to false, no gesture will be detected at all, so /// [onPopInvoked] will not be called. Programmatically attempting pop /// navigation will also result in a call to [onPopInvoked], with `didPop` /// indicating success or failure. /// /// If [canPop] is true, then a system back gesture will cause the enclosing /// [Navigator] to receive a pop as usual. [onPopInvoked] will be called with /// `didPop` as true, unless the pop failed for reasons unrelated to /// [PopScope], in which case it will be false. /// /// {@tool dartpad} /// This sample demonstrates showing a confirmation dialog before navigating /// away from a page. /// /// ** See code in examples/api/lib/widgets/pop_scope/pop_scope.0.dart ** /// {@end-tool} /// /// See also: /// /// * [NavigatorPopHandler], which is a less verbose way to handle system back /// gestures in simple cases of nested [Navigator]s. /// * [Form.canPop] and [Form.onPopInvoked], which can be used to handle system /// back gestures in the case of a form with unsaved data. /// * [ModalRoute.registerPopEntry] and [ModalRoute.unregisterPopEntry], /// which this widget uses to integrate with Flutter's navigation system. class PopScope extends StatefulWidget { /// Creates a widget that registers a callback to veto attempts by the user to /// dismiss the enclosing [ModalRoute]. const PopScope({ super.key, required this.child, this.canPop = true, this.onPopInvoked, }); /// The widget below this widget in the tree. /// /// {@macro flutter.widgets.ProxyWidget.child} final Widget child; /// {@template flutter.widgets.PopScope.onPopInvoked} /// Called after a route pop was handled. /// {@endtemplate} /// /// It's not possible to prevent the pop from happening at the time that this /// method is called; the pop has already happened. Use [canPop] to /// disable pops in advance. /// /// This will still be called even when the pop is canceled. A pop is canceled /// when the relevant [Route.popDisposition] returns false, such as when /// [canPop] is set to false on a [PopScope]. The `didPop` parameter /// indicates whether or not the back navigation actually happened /// successfully. /// /// See also: /// /// * [Route.onPopInvoked], which is similar. final PopInvokedCallback? onPopInvoked; /// {@template flutter.widgets.PopScope.canPop} /// When false, blocks the current route from being popped. /// /// This includes the root route, where upon popping, the Flutter app would /// exit. /// /// If multiple [PopScope] widgets appear in a route's widget subtree, then /// each and every `canPop` must be `true` in order for the route to be /// able to pop. /// /// [Android's predictive back](https://developer.android.com/guide/navigation/predictive-back-gesture) /// feature will not animate when this boolean is false. /// {@endtemplate} final bool canPop; @override State<PopScope> createState() => _PopScopeState(); } class _PopScopeState extends State<PopScope> implements PopEntry { ModalRoute<dynamic>? _route; @override PopInvokedCallback? get onPopInvoked => widget.onPopInvoked; @override late final ValueNotifier<bool> canPopNotifier; @override void initState() { super.initState(); canPopNotifier = ValueNotifier<bool>(widget.canPop); } @override void didChangeDependencies() { super.didChangeDependencies(); final ModalRoute<dynamic>? nextRoute = ModalRoute.of(context); if (nextRoute != _route) { _route?.unregisterPopEntry(this); _route = nextRoute; _route?.registerPopEntry(this); } } @override void didUpdateWidget(PopScope oldWidget) { super.didUpdateWidget(oldWidget); canPopNotifier.value = widget.canPop; } @override void dispose() { _route?.unregisterPopEntry(this); canPopNotifier.dispose(); super.dispose(); } @override Widget build(BuildContext context) => widget.child; }