blob: 5985fa86fddd9d51483a12ef7833c9336c759141 [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 'routes.dart';
/// Registers a callback to veto attempts by the user to dismiss the enclosing
/// [ModalRoute].
///
/// {@tool snippet --template=stateful_widget}
///
/// Whenever the back button is pressed, you will get a callback at [onWillPop],
/// which returns a [Future]. If the [Future] returns true, the screen is
/// popped.
///
/// ```dart
/// bool shouldPop = true;
/// @override
/// Widget build(BuildContext context) {
/// return WillPopScope (
/// onWillPop: () async {
/// return shouldPop;
/// },
/// child: const Text('WillPopScope sample'),
/// );
/// }
/// ```
/// {@end-tool}
///
/// {@tool dartpad --template=stateful_widget_material}
/// ```dart
/// bool shouldPop = true;
/// @override
/// Widget build(BuildContext context) {
/// return WillPopScope(
/// onWillPop: () async {
/// return shouldPop;
/// },
/// child: Scaffold(
/// appBar: AppBar(
/// title: const Text('Flutter WillPopScope demo'),
/// ),
/// body: Center(
/// child: Column(
/// mainAxisAlignment: MainAxisAlignment.center,
/// children: <Widget>[
/// OutlinedButton(
/// child: const Text('Push'),
/// onPressed: () {
/// Navigator.of(context).push<void>(
/// MaterialPageRoute<void>(
/// builder: (BuildContext context) {
/// return const MyStatefulWidget();
/// },
/// ),
/// );
/// },
/// ),
/// OutlinedButton(
/// child: Text('shouldPop: $shouldPop'),
/// onPressed: () {
/// setState(
/// () {
/// shouldPop = !shouldPop;
/// },
/// );
/// },
/// ),
/// const Text('Push to a new screen, then tap on shouldPop '
/// 'button to toggle its value. Press the back '
/// 'button in the appBar to check its behaviour '
/// 'for different values of shouldPop'),
/// ],
/// ),
/// ),
/// ),
/// );
/// }
/// ```
///
/// {@end-tool}
///
/// See also:
///
/// * [ModalRoute.addScopedWillPopCallback] and [ModalRoute.removeScopedWillPopCallback],
/// which this widget uses to register and unregister [onWillPop].
/// * [Form], which provides an `onWillPop` callback that enables the form
/// to veto a `pop` initiated by the app's back button.
///
class WillPopScope extends StatefulWidget {
/// Creates a widget that registers a callback to veto attempts by the user to
/// dismiss the enclosing [ModalRoute].
///
/// The [child] argument must not be null.
const WillPopScope({
Key? key,
required this.child,
required this.onWillPop,
}) : assert(child != null),
super(key: key);
/// The widget below this widget in the tree.
///
/// {@macro flutter.widgets.ProxyWidget.child}
final Widget child;
/// Called to veto attempts by the user to dismiss the enclosing [ModalRoute].
///
/// If the callback returns a Future that resolves to false, the enclosing
/// route will not be popped.
final WillPopCallback? onWillPop;
@override
_WillPopScopeState createState() => _WillPopScopeState();
}
class _WillPopScopeState extends State<WillPopScope> {
ModalRoute<dynamic>? _route;
@override
void didChangeDependencies() {
super.didChangeDependencies();
if (widget.onWillPop != null)
_route?.removeScopedWillPopCallback(widget.onWillPop!);
_route = ModalRoute.of(context);
if (widget.onWillPop != null)
_route?.addScopedWillPopCallback(widget.onWillPop!);
}
@override
void didUpdateWidget(WillPopScope oldWidget) {
super.didUpdateWidget(oldWidget);
assert(_route == ModalRoute.of(context));
if (widget.onWillPop != oldWidget.onWillPop && _route != null) {
if (oldWidget.onWillPop != null)
_route!.removeScopedWillPopCallback(oldWidget.onWillPop!);
if (widget.onWillPop != null)
_route!.addScopedWillPopCallback(widget.onWillPop!);
}
}
@override
void dispose() {
if (widget.onWillPop != null)
_route?.removeScopedWillPopCallback(widget.onWillPop!);
super.dispose();
}
@override
Widget build(BuildContext context) => widget.child;
}